├── .gitignore ├── .gitmodules ├── README.md ├── Test.hs ├── benchmark └── sum.rus ├── lib └── prelude.rusi ├── rusc ├── LICENSE ├── Setup.hs ├── rusc.cabal ├── src │ ├── Language │ │ └── RuScript │ │ │ ├── AST.hs │ │ │ ├── ByteCode.hs │ │ │ ├── Codegen.hs │ │ │ ├── Desugar.hs │ │ │ ├── Import.hs │ │ │ ├── Optimize'.hs │ │ │ ├── Optimize.hs │ │ │ ├── Option.hs │ │ │ ├── Parser.hs │ │ │ ├── Serialize.hs │ │ │ ├── StaticCheck.hs │ │ │ ├── Traversal.hs │ │ │ └── optimize.v │ ├── Main.hs │ └── MultiplateDerive.hs └── stack.yaml ├── rvm ├── Cargo.toml ├── LICENSE └── src │ ├── bytecode.rs │ ├── class.rs │ ├── cmdopt.rs │ ├── deserialize.rs │ ├── dispatch.rs │ ├── env.rs │ ├── function.rs │ ├── instance.rs │ ├── interpret.rs │ ├── lib.rs │ ├── main.rs │ ├── object.rs │ └── primitives.rs ├── spec ├── Bytecode.md ├── Compiler.md ├── Model.md └── machine │ └── machine.md └── tests ├── add.rus ├── conc.rus ├── control.rus ├── inheritance.rus ├── invoke.rus ├── list.rus ├── mod.rus ├── nil.rus └── vis.rus /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | Cargo.lock 3 | .DS_Store 4 | .stack-work 5 | *.rusb 6 | dist -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "rust-gc"] 2 | path = rust-gc 3 | url = https://github.com/Manishearth/rust-gc 4 | [submodule "multiplate"] 5 | path = multiplate 6 | url = https://github.com/izgzhen/multiplate 7 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | RuScript 2 | ------ 3 | 4 | ## Build 5 | 6 | Tool-chain dependencies: Rust (nightly), Haskell (8.01) 7 | 8 | 1. `git submodule update` to fetch `rust-gc` dev source 9 | 2. `runghc Test.hs` to run unit tests 10 | 11 | To build the `rvm` and `rusc` separately: 12 | 13 | 1. `rvm`: `cd rvm; cargo build` 14 | 2. `rusc`: `cd rusc; stack build` 15 | 16 | ## Features 17 | 18 | * Safe runtime written in Rust with garbage collection 19 | * Lightweight stack-based bytecode 20 | * High-level object-oriented language 21 | * Static checking module 22 | 23 | ## Design 24 | Please refer to `spec` 25 | 26 | ## TODOs 27 | * `.rusi` Interface parser 28 | + Note #1: Whether or not to attach a `self` to each method signature 29 | + Note #2: Separate compilation and selective exports require more engineering, of which I have little experience in 30 | * Concurrent primitives 31 | + Note #1: We need to construct runners inside the interpreters, and every runner is contained in a thread and responsible for a user-thread 32 | + Note #2: To share the data between threads, we need a concurrent GC system, which we don't really have now. 33 | * Formal Specification Revision based on Coq 34 | 35 | ## Performance Issue 36 | The current performance revealed by benchmarking the `sum.rus` is very poor, taking as much as 100 times longer than a naïve Python implementation. 37 | 38 | Currently, the source of latency can be contributed to three aspects: 39 | 40 | 1. GC 41 | 2. Interpreter 42 | 3. Compiler 43 | 44 | Actually, by increasing the instruction set, we can eliminate many "stupid" instruction sequence, such as `POP` then `PUSH`, which is equivalent to a write stack-top to local var without popping out. 45 | -------------------------------------------------------------------------------- /Test.hs: -------------------------------------------------------------------------------- 1 | -- test script, which should be run by `runghc` under the `rusc` directory 2 | -- TODO: More general 3 | 4 | module Test where 5 | 6 | import System.FilePath (()) 7 | import System.Process 8 | import System.Exit 9 | 10 | data Result = NormalStdOut String 11 | | CompilerErrorOut String 12 | 13 | ruscExecutable :: String 14 | ruscExecutable = "rusc/.stack-work/dist/x86_64-osx/Cabal-1.24.0.0/build/rusc/rusc" 15 | 16 | testsDir :: String 17 | testsDir = "tests" 18 | 19 | rvmExecutable :: String 20 | rvmExecutable = "rvm/target/debug/main" 21 | 22 | testList :: [(String, String, Result)] -- Name, stdin, expected stdout 23 | testList = [ ("inheritance", "", NormalStdOut "1") 24 | , ("add", "", NormalStdOut "3") 25 | , ("control", "", NormalStdOut "2") 26 | , ("nil", "", NormalStdOut "nil") 27 | , ("list", "", NormalStdOut "[1]") 28 | , ("invoke", "", NormalStdOut "6") 29 | , ("mod", "", NormalStdOut "1") 30 | , ("vis", "", CompilerErrorOut "Error in checking: accessing private attribute pri\n") ] 31 | 32 | main :: IO () 33 | main = do 34 | putStrLn $ "Building artifacts..." 35 | -- Compile VM 36 | _ <- readCreateProcess ((shell "cargo build -q") { cwd = Just "rvm" }) "" 37 | -- Compile Compiler 38 | _ <- readCreateProcess ((shell "stack build") { cwd = Just "rusc" }) "" 39 | 40 | putStrLn $ "Testing " ++ show (length testList) ++ " cases..." 41 | flip mapM_ testList $ \(name, stdin, expected) -> do 42 | let source = name ++ ".rus" 43 | let binary = name ++ ".rusb" 44 | -- Compile 45 | (ecode, out, err) <- readProcessWithExitCode 46 | ruscExecutable 47 | [source, binary, "-i", testsDir] 48 | "" 49 | case ecode of 50 | ExitSuccess -> do 51 | -- Run 52 | stdout <- readProcess rvmExecutable [testsDir binary] stdin 53 | case expected of 54 | NormalStdOut eout -> 55 | if eout == stdout 56 | then putStrLn $ "+ Test passed: " ++ name 57 | else putStrLn $ "+ Test failed: " ++ name ++ 58 | ", because \"" ++ stdout ++ 59 | "\" is not the expected \"" ++ eout ++ "\"" 60 | CompilerErrorOut s -> 61 | putStrLn $ "+ Test failed: " ++ name ++ 62 | ", because failure \"" ++ s ++ "\" is expected" 63 | ExitFailure i -> case expected of 64 | CompilerErrorOut s -> 65 | if s == err 66 | then putStrLn $ "+ Test passed: " ++ name 67 | else putStrLn $ "+ Test failed: " ++ name ++ 68 | ", because \"" ++ err ++ 69 | "\" is not the expected failure \"" ++ s ++ "\"" 70 | _ -> putStrLn $ "+ Test failed: " ++ name ++ 71 | ", because it is expected to success, but fail with code \"" ++ 72 | show i ++ ": " ++ err ++ "\"" 73 | -------------------------------------------------------------------------------- /benchmark/sum.rus: -------------------------------------------------------------------------------- 1 | # Sum over 1 .. n 2 | # n = 5000, 3.155 s 3 | # n = 10000, 6.129 s 4 | # n = 20000, 11.959 s 5 | 6 | # Comparison with Python: 7 | # n = 500000, 3.552 s 8 | # n = 5000000, 35.061 s 9 | 10 | var i : Int = 1; 11 | var sum : Int = 0; 12 | 13 | while ((i <= 100)) { 14 | sum = (sum + i); 15 | i = (i + 1); 16 | } 17 | 18 | sum.print(); 19 | -------------------------------------------------------------------------------- /lib/prelude.rusi: -------------------------------------------------------------------------------- 1 | # RuScript Interface File as Prelude 2 | 3 | type Int { 4 | fn add(x:Int) -> Int; 5 | fn print(); 6 | fn le(x:Int) -> Bool; 7 | } 8 | 9 | type Bool { 10 | fn print(); 11 | fn not() -> Bool; 12 | } 13 | 14 | type Nil { 15 | fn print(); 16 | } 17 | 18 | type Str { 19 | fn print(); 20 | } 21 | 22 | type List { 23 | fn print(); 24 | fn cons(x: a) -> List; 25 | } 26 | 27 | -------------------------------------------------------------------------------- /rusc/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015 Zhen Zhang 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included 12 | in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 17 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 18 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 19 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 20 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /rusc/Setup.hs: -------------------------------------------------------------------------------- 1 | import Distribution.Simple 2 | main = defaultMain 3 | -------------------------------------------------------------------------------- /rusc/rusc.cabal: -------------------------------------------------------------------------------- 1 | name: rusc 2 | version: 0.3.2 3 | synopsis: Compiler for RuScript 4 | -- description: 5 | license: MIT 6 | license-file: LICENSE 7 | author: Zhen Zhang 8 | maintainer: izgzhen@gmail.com 9 | -- copyright: 10 | category: Language 11 | build-type: Simple 12 | -- extra-source-files: 13 | cabal-version: >=1.10 14 | 15 | executable rusc 16 | main-is: Main.hs 17 | other-modules: Language.RuScript.Codegen 18 | Language.RuScript.Parser 19 | Language.RuScript.Serialize 20 | Language.RuScript.AST 21 | Language.RuScript.ByteCode 22 | Language.RuScript.StaticCheck 23 | Language.RuScript.Option 24 | Language.RuScript.Desugar 25 | Language.RuScript.Optimize 26 | Language.RuScript.Import 27 | Language.RuScript.Traversal 28 | MultiplateDerive 29 | other-extensions: FlexibleContexts 30 | build-depends: base >=4.9 && <4.10, 31 | mtl >=2.2 && <2.3, 32 | containers >=0.5 && <0.6, 33 | parsec >=3.1 && <3.2, 34 | bytestring, 35 | binary, 36 | utf8-string, 37 | split, 38 | vector, 39 | lens, 40 | filepath, 41 | multiplate, 42 | template-haskell, 43 | transformers 44 | hs-source-dirs: src 45 | default-language: Haskell2010 46 | ghc-options: -Wall 47 | -------------------------------------------------------------------------------- /rusc/src/Language/RuScript/AST.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE FlexibleInstances #-} 2 | 3 | module Language.RuScript.AST where 4 | 5 | type Name = String 6 | 7 | data Qualified = Qualified [String] String 8 | deriving (Eq, Ord) 9 | 10 | type Binding = (Name, Type) 11 | 12 | -- Type 13 | data Type = TyNil 14 | | TyInt 15 | | TyBool 16 | | TyStr 17 | | TyList Type 18 | | TyClass Qualified 19 | | TyBot -- undetermined type 20 | deriving (Show, Eq, Ord) 21 | 22 | -- Basic Block 23 | data Block = Branch Expr [Statement] [Statement] 24 | | Loop Expr [Statement] 25 | deriving (Show, Eq) 26 | 27 | -- Expression 28 | data Expr = EVar Name 29 | | EGet Name Name 30 | | EInvoke Expr Name [Expr] 31 | | ECall Qualified [Expr] 32 | | ENew Qualified [Expr] 33 | | ELit Literal 34 | | ETerm Term 35 | deriving (Show, Eq) 36 | 37 | -- Term. It should be desugared into invoke 38 | data Term = TPlus Expr Expr 39 | | TLE Expr Expr 40 | deriving (Show, Eq) 41 | 42 | -- Literal Value Constructor 43 | data Literal = LStr String 44 | | LBool Bool 45 | | LInt Int 46 | | LList 47 | | LNil 48 | deriving (Show, Eq) 49 | 50 | -- Linear statement 51 | data Statement = SVar Binding (Maybe Expr) 52 | | SAssign LHS Expr 53 | | SBlock Block 54 | | SInvoke Expr Name [Expr] 55 | | SCall Qualified [Expr] 56 | | SReturn Expr 57 | | SBreak 58 | deriving (Show, Eq) 59 | 60 | -- Left hand side 61 | data LHS = -- Variable, e.g. x 62 | LVar Name 63 | -- Setter e.g. x.y 64 | | LAttr Name Name 65 | deriving (Show, Eq) 66 | 67 | -- Top-level construct 68 | data FnSig = FnSig Qualified [Binding] (Maybe Type) deriving (Show, Eq) 69 | 70 | data Declaration = -- Function declaration 71 | FnDecl FnSig [Statement] 72 | -- Class declaration 73 | | ClassDecl Qualified (Maybe Qualified) [(Visibility, Attr)] [(Visibility, Method)] 74 | -- e.g. after `import std`, the class `std.pair` can be used 75 | | ImportDecl [String] 76 | deriving (Show, Eq) 77 | 78 | data Visibility = Public | Private deriving (Show, Eq) 79 | 80 | type Attr = Binding 81 | 82 | data Method = Virtual FnSig 83 | | Concrete FnSig [Statement] 84 | deriving (Show, Eq) 85 | 86 | data Program = Program [Either Statement Declaration] deriving (Show, Eq) 87 | 88 | 89 | instance Show Qualified where 90 | show (Qualified [] x) = x 91 | show (Qualified ss x) = splitByDot ss ++ "." ++ x 92 | where 93 | splitByDot [] = error "unexpected" 94 | splitByDot [a] = a 95 | splitByDot (a:as) = a ++ "." ++ splitByDot as 96 | 97 | 98 | -------------------------------------------------------------------------------- /rusc/src/Language/RuScript/ByteCode.hs: -------------------------------------------------------------------------------- 1 | module Language.RuScript.ByteCode ( 2 | ByteCode(..) 3 | , Label 4 | , initLabel 5 | , genLabel 6 | , Pos 7 | , Address 8 | ) where 9 | 10 | data ByteCode = CALL Int 11 | | INVOKE String 12 | | RET 13 | -- * Address should be `Pos` after pass one 14 | | JUMP Address 15 | | JUMPT Address 16 | | JUMPF Address 17 | -- * 18 | | PUSH Int 19 | | POP Int 20 | | NEW Int 21 | | PUSHA String 22 | | POPA String 23 | | PUSHSTR String 24 | | PUSHINT Int 25 | | PUSHBOOL Int 26 | | CLASS Int Int Int 27 | | SFUNC 28 | | EBODY Int 29 | -- * Extensions 30 | | PUSHLIST 31 | | PUSHNIL 32 | deriving (Show, Eq) 33 | 34 | 35 | -- Label 36 | 37 | newtype Label = Label Int -- Label no. 38 | deriving (Show, Eq, Ord) 39 | 40 | initLabel :: Label 41 | initLabel = Label 0 42 | 43 | genLabel :: Label -> (Label, Label) 44 | genLabel (Label i) = (Label i, Label $ i + 1) 45 | 46 | -- Position in the bytecode array 47 | type Pos = Int 48 | 49 | -- Abstract address is either a absolute position, or a label 50 | type Address = Either Pos Label 51 | 52 | -------------------------------------------------------------------------------- /rusc/src/Language/RuScript/Codegen.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE LambdaCase, TemplateHaskell #-} 2 | 3 | module Language.RuScript.Codegen where 4 | 5 | import Control.Monad.State 6 | import qualified Data.Vector as V 7 | import qualified Data.Map as M 8 | import Data.Either 9 | import Control.Lens 10 | import Data.Maybe (fromJust) 11 | 12 | import Language.RuScript.AST 13 | import Language.RuScript.ByteCode 14 | 15 | -- The enclosed loop's entry position and exit label 16 | type EnclosedLoop = (Pos, Label) 17 | 18 | -- Codegen State Monad 19 | type Codegen = State CodegenState 20 | 21 | data CodegenState = CodegenState { 22 | _classTable :: M.Map Qualified Int, 23 | _funcTable :: M.Map Qualified Int, 24 | _bytecode :: V.Vector ByteCode, 25 | _enclosed :: Maybe EnclosedLoop, 26 | _labelPos :: M.Map Label Pos, 27 | _labelCounter :: Label, 28 | _symbolTable :: M.Map Name Int 29 | } 30 | 31 | initState :: CodegenState 32 | initState = CodegenState M.empty M.empty V.empty Nothing M.empty initLabel M.empty 33 | 34 | makeLenses ''CodegenState 35 | 36 | -- flatten a block into the linear address space, return 37 | -- the next new address after the allocated space 38 | 39 | printCode :: [ByteCode] -> IO () 40 | printCode v = mapM_ (\(i, c) -> putStrLn $ show i ++ " : " ++ show c) $ zip [0..] v 41 | 42 | runCodegen :: ToByteCode a => a -> [ByteCode] 43 | runCodegen a = let s = execState (flatten a) initState 44 | in V.toList $ delabel (_bytecode s) (_labelPos s) 45 | 46 | delabel :: V.Vector ByteCode -> M.Map Label Pos -> V.Vector ByteCode 47 | delabel v m = V.imap f v 48 | where 49 | f i (JUMP (Right lbl)) = JUMP $ Left (fromJust (M.lookup lbl m) - i - 1) 50 | f i (JUMPF (Right lbl)) = JUMPF $ Left (fromJust (M.lookup lbl m) - i - 1) 51 | f i (JUMPT (Right lbl)) = JUMPT $ Left (fromJust (M.lookup lbl m) - i - 1) 52 | f _ other = other 53 | 54 | 55 | class ToByteCode a where 56 | flatten :: a -> Codegen () 57 | 58 | instance ToByteCode Block where 59 | flatten (Branch expr b1 b2) = do 60 | flatten expr 61 | l1 <- jumpf -- JUMPF 62 | flatten b1 63 | l2 <- jump -- JUMP 64 | mark l1 -- substantiate l1 to current position 65 | flatten b2 66 | mark l2 67 | 68 | flatten (Loop expr b) = do 69 | entry <- getPos 70 | 71 | flatten expr 72 | exit <- jumpf 73 | inLoop entry exit $ flatten b 74 | p <- getPos 75 | emit $ JUMP $ Left (entry - p - 1) 76 | mark exit 77 | 78 | 79 | instance ToByteCode Expr where 80 | flatten (EVar x) = pushVar x 81 | 82 | flatten (EGet x attr) = do 83 | pushVar x 84 | emit $ PUSHA attr 85 | 86 | flatten (ECall f exprs) = call f exprs 87 | 88 | flatten (EInvoke x f exprs) = invoke x f exprs 89 | 90 | flatten (ENew c exprs) = do 91 | flatten $ reverse exprs 92 | new c 93 | 94 | flatten (ELit lit) = do 95 | case lit of 96 | LStr s -> emit $ PUSHSTR s 97 | LInt i -> emit $ PUSHINT i 98 | LBool b -> if b then emit (PUSHBOOL 1) else emit (PUSHBOOL 0) 99 | LList -> emit PUSHLIST 100 | LNil -> emit PUSHNIL 101 | 102 | instance ToByteCode Statement where 103 | flatten (SVar (x, _) Nothing) = addVar x 104 | flatten (SVar (x, _) (Just expr)) = do 105 | addVar x 106 | flatten expr 107 | popVar x 108 | 109 | flatten (SAssign lhs expr) = do 110 | case lhs of 111 | LVar x -> flatten expr >> popVar x 112 | LAttr x attr -> do 113 | popVar x 114 | flatten expr 115 | emit $ POPA attr 116 | 117 | flatten (SBlock b) = flatten b 118 | flatten (SReturn expr) = do 119 | flatten expr 120 | emit RET 121 | flatten SBreak = withLoop $ \_ exitLabel -> emit $ JUMP (Right exitLabel) 122 | 123 | flatten (SInvoke x f exprs) = invoke x f exprs 124 | flatten (SCall f exprs) = call f exprs 125 | 126 | invoke :: Expr -> Name -> [Expr] -> Codegen () 127 | invoke expr f exprs = do 128 | let exprs' = reverse exprs 129 | flatten exprs' 130 | flatten expr 131 | emit $ INVOKE f 132 | 133 | call :: Qualified -> [Expr] -> Codegen () 134 | call f exprs = do 135 | let exprs' = reverse exprs 136 | flatten exprs' 137 | i <- lookupFunc f 138 | emit $ CALL i 139 | 140 | 141 | instance ToByteCode Declaration where 142 | flatten (ImportDecl _) = return () 143 | flatten (FnDecl (FnSig name bindings _) stmts) = do 144 | addFunc name 145 | emit SFUNC 146 | flattenFunc Nothing name bindings stmts 147 | 148 | flatten (ClassDecl x mFather attrs methods) = do 149 | addClass x 150 | let concretes = filter (isConcrete . snd) methods 151 | 152 | father_idx <- case mFather of 153 | Just i -> lookupClass i 154 | Nothing -> return (-1) 155 | emit $ CLASS (length attrs) (length concretes) father_idx 156 | forM_ attrs $ \(_, (s, _)) -> emit $ PUSHSTR s 157 | forM_ concretes $ \(_, (Concrete (FnSig name@(Qualified _ baseName) bindings _) stmts)) -> do 158 | flattenFunc (Just x) name bindings stmts 159 | emit $ PUSHSTR baseName 160 | 161 | instance ToByteCode a => ToByteCode [a] where 162 | flatten = mapM_ flatten 163 | 164 | flattenFunc :: Maybe Qualified -> Qualified -> [Binding] -> [Statement] -> Codegen () 165 | flattenFunc mClsName _ bindings stmts = do 166 | nLocals <- inScope mClsName bindings $ do 167 | flatten stmts 168 | nLocals <- M.size <$> use symbolTable 169 | return nLocals 170 | emit $ EBODY nLocals 171 | 172 | inScope :: Maybe Qualified -> [Binding] -> Codegen a -> Codegen a 173 | inScope mClsName bindings gen = do 174 | let bindings' = case mClsName of 175 | Just clsName -> ("self", TyClass clsName) : bindings 176 | Nothing -> bindings 177 | 178 | oldTable <- use symbolTable 179 | symbolTable .= M.fromList (zip (map fst bindings') [0..]) 180 | forM_ [0..(length bindings' - 1)] $ emit . POP 181 | ret <- gen 182 | symbolTable .= oldTable 183 | return ret 184 | 185 | instance ToByteCode Program where 186 | flatten (Program mixed) = do 187 | let declarations = rights mixed 188 | let topStmts = lefts mixed 189 | flatten declarations 190 | flatten topStmts 191 | nLocals <- M.size <$> use symbolTable 192 | emit $ PUSHINT nLocals 193 | 194 | -- Helpers 195 | 196 | write :: Pos -> ByteCode -> Codegen () 197 | write i b = bytecode %= update' i b 198 | 199 | mkLabel :: Codegen Label 200 | mkLabel = do 201 | (ret, l') <- genLabel <$> use labelCounter 202 | labelCounter .= l' 203 | return ret 204 | 205 | substantiate :: Label -> Pos -> Codegen () 206 | substantiate lbl pos = labelPos %= M.insert lbl pos 207 | 208 | inLoop :: Pos -> Label -> Codegen a -> Codegen a 209 | inLoop pos lbl gen = do 210 | e <- use enclosed 211 | enclosed .= Just (pos, lbl) 212 | a <- gen 213 | enclosed .= e 214 | return a 215 | 216 | jumpt :: Codegen Label 217 | jumpt = jumpGen JUMPT 218 | 219 | jump :: Codegen Label 220 | jump = jumpGen JUMP 221 | 222 | jumpf :: Codegen Label 223 | jumpf = jumpGen JUMPF 224 | 225 | jumpGen :: (Address -> ByteCode) -> Codegen Label 226 | jumpGen constr = do 227 | l <- mkLabel 228 | emit $ constr $ Right l 229 | return l 230 | 231 | mark :: Label -> Codegen () 232 | mark l = getPos >>= substantiate l 233 | 234 | getPos :: Codegen Pos 235 | getPos = V.length <$> use bytecode 236 | 237 | withVar :: (Int -> ByteCode) -> Name -> Codegen () 238 | withVar constr name = do 239 | t <- use symbolTable 240 | case M.lookup name t of 241 | Just idx -> emit $ constr idx 242 | Nothing -> error $ "use name before declaration: " ++ show name 243 | 244 | addVar :: Name -> Codegen () 245 | addVar name = symbolTable %= \t -> 246 | case M.lookup name t of 247 | Just _ -> t 248 | Nothing -> M.insert name (M.size t) t 249 | 250 | popVar :: Name -> Codegen () 251 | popVar = withVar POP 252 | 253 | pushVar :: Name -> Codegen () 254 | pushVar = withVar PUSH 255 | 256 | emit :: ByteCode -> Codegen () 257 | emit code = bytecode %= flip V.snoc code 258 | 259 | new :: Qualified -> Codegen () 260 | new x = lookupClass x >>= emit . NEW 261 | 262 | withLoop :: (Pos -> Label -> Codegen a) -> Codegen a 263 | withLoop callback = do 264 | mEnclosed <- use enclosed 265 | case mEnclosed of 266 | Just (pos, lbl) -> callback pos lbl 267 | Nothing -> error "not in enclosed loop" 268 | 269 | isConcrete :: Method -> Bool 270 | isConcrete (Concrete _ _) = True 271 | isConcrete _ = False 272 | 273 | lookupClass :: Qualified -> Codegen Int 274 | lookupClass name = do 275 | t <- use classTable 276 | case M.lookup name t of 277 | Nothing -> error $ "can't find class " ++ show name 278 | Just idx -> return idx 279 | 280 | addClass :: Qualified -> Codegen () 281 | addClass name = classTable %= (\t -> M.insert name (M.size t) t) 282 | 283 | lookupFunc :: Qualified -> Codegen Int 284 | lookupFunc name = do 285 | t <- use funcTable 286 | case M.lookup name t of 287 | Nothing -> error $ "can't find class " ++ show name 288 | Just idx -> return idx 289 | 290 | addFunc :: Qualified -> Codegen () 291 | addFunc name = funcTable %= (\t -> M.insert name (M.size t) t) 292 | 293 | 294 | update' :: Int -> a -> V.Vector a -> V.Vector a 295 | update' i a v = v V.// [(i, a)] 296 | -------------------------------------------------------------------------------- /rusc/src/Language/RuScript/Desugar.hs: -------------------------------------------------------------------------------- 1 | module Language.RuScript.Desugar where 2 | 3 | import Language.RuScript.AST 4 | 5 | desugarTerm :: Term -> Expr 6 | desugarTerm (TPlus e1 e2) = EInvoke e1 "add" [e2] 7 | desugarTerm (TLE e1 e2) = EInvoke e1 "le" [e2] 8 | -------------------------------------------------------------------------------- /rusc/src/Language/RuScript/Import.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE LambdaCase #-} 2 | -- Import system 3 | 4 | module Language.RuScript.Import where 5 | 6 | import System.FilePath (()) 7 | import System.IO 8 | import System.Exit 9 | import Data.Either (lefts, rights) 10 | import Data.Generics.Multiplate 11 | import Data.Functor.Identity 12 | import Control.Arrow (second) 13 | 14 | import Language.RuScript.AST 15 | import Language.RuScript.StaticCheck 16 | import Language.RuScript.Parser 17 | import Language.RuScript.Traversal 18 | 19 | -- all exposed (which is just everything) declarations as AST will be brought out 20 | importModule :: FilePath -> [String] -> IO [Declaration] 21 | importModule includeDir segments = do 22 | let path = includeDir foldr () "" segments ++ ".rus" 23 | source <- readFile path 24 | case parseProgram source of 25 | Left err -> exitError $ "Error in parsing: " ++ show err 26 | Right ast -> do 27 | case checkProgram ast of 28 | Left err -> exitError $ "Error in checking: " ++ err 29 | Right _ -> do 30 | let (otherDecls, imports) = splitDecls ast 31 | decls <- concat <$> mapM (importModule includeDir) imports 32 | return $ map (qualify segments) otherDecls ++ decls 33 | 34 | exitError :: String -> IO a 35 | exitError s = hPutStrLn stderr s >> exitFailure 36 | 37 | splitDecls :: Program -> ([Declaration], [[String]]) 38 | splitDecls (Program mix) = 39 | let decls = rights mix 40 | sss = flip map decls $ \case 41 | ImportDecl s -> Right s 42 | other -> Left other 43 | in (lefts sss, rights sss) 44 | 45 | resolveDeps :: FilePath -> Program -> IO Program 46 | resolveDeps includeDir program@(Program mixed) = do 47 | let (_, imports) = splitDecls program 48 | decls <- concat <$> mapM (importModule includeDir) imports 49 | return $ Program (mixed ++ map Right decls) 50 | 51 | class Qualifiable a where 52 | qualify :: [String] -> a -> a 53 | 54 | instance Qualifiable a => Qualifiable [a] where 55 | qualify qualifier xs = map (qualify qualifier) xs 56 | 57 | instance Qualifiable Declaration where 58 | qualify qualifier = \case 59 | ImportDecl _ -> error "unexpected ImportDecl in qualification process" 60 | FnDecl sig stmts -> FnDecl (qualify qualifier sig) (qualify qualifier stmts) 61 | ClassDecl (Qualified _ name) mInherit attrs mtds -> 62 | let mInherit' = qualify qualifier <$> mInherit 63 | mtds' = flip map mtds $ \(vis, mtd) -> case mtd of 64 | Virtual sig -> (vis, Virtual (qualify qualifier sig)) 65 | Concrete sig body -> (vis, Concrete (qualify qualifier sig) 66 | (qualify qualifier body)) 67 | in ClassDecl (Qualified qualifier name) mInherit' attrs mtds' 68 | 69 | instance Qualifiable Statement where 70 | qualify qualifier = mapper (qualifyPlate qualifier) statement_ 71 | 72 | instance Qualifiable Type where 73 | qualify qualifier = mapper (qualifyPlate qualifier) type_ 74 | 75 | instance Qualifiable Qualified where 76 | qualify qualifier = \case 77 | Qualified [] name -> Qualified qualifier name 78 | other -> other 79 | 80 | instance Qualifiable FnSig where 81 | qualify qualifier (FnSig qualified bindings mRetTy) = 82 | FnSig (qualify qualifier qualified) 83 | (map (second $ qualify qualifier) bindings) 84 | (qualify qualifier <$> mRetTy) 85 | 86 | qualifyPlate :: [String] -> Plate Identity 87 | qualifyPlate qualifier = purePlate { qualified_ = pure . qualify qualifier } 88 | 89 | -------------------------------------------------------------------------------- /rusc/src/Language/RuScript/Optimize'.hs: -------------------------------------------------------------------------------- 1 | module Optimize' where 2 | 3 | import qualified Prelude 4 | 5 | data ByteCode z = 6 | PUSH z 7 | | POP z 8 | 9 | -------------------------------------------------------------------------------- /rusc/src/Language/RuScript/Optimize.hs: -------------------------------------------------------------------------------- 1 | module Language.RuScript.Optimize where 2 | 3 | 4 | import Language.RuScript.ByteCode 5 | import Language.RuScript.Traversal 6 | 7 | 8 | -- POP after PUSH optimization 9 | optimize :: [ByteCode] -> [ByteCode] 10 | optimize bs@(PUSH i : POP i' : bs') 11 | | i == i' = bs' 12 | | otherwise = bs 13 | optimize bs = bs 14 | 15 | -------------------------------------------------------------------------------- /rusc/src/Language/RuScript/Option.hs: -------------------------------------------------------------------------------- 1 | -- Command line options 2 | 3 | module Language.RuScript.Option where 4 | 5 | data Option = Option { 6 | _debugOpt :: Bool 7 | , _optimizeOpt :: Bool 8 | , _includeOpt :: String 9 | } deriving (Show, Eq) 10 | 11 | defaultOpt :: Option 12 | defaultOpt = Option { 13 | _debugOpt = False 14 | , _optimizeOpt = False 15 | , _includeOpt = "" 16 | } 17 | 18 | parseOpt :: [String] -> Option 19 | parseOpt as = parseOpt' as defaultOpt 20 | where 21 | parseOpt' ("-debug":args) opt = parseOpt' args $ opt { _debugOpt = True } 22 | parseOpt' ("-o":args) opt = parseOpt' args $ opt { _optimizeOpt = True } 23 | parseOpt' ("-i":i:args) opt = parseOpt' args $ opt { _includeOpt = i } 24 | parseOpt' (x:args) opt = error $ "undefined option: " ++ show x 25 | parseOpt' _ opt = opt 26 | -------------------------------------------------------------------------------- /rusc/src/Language/RuScript/Parser.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE FlexibleContexts #-} 2 | 3 | module Language.RuScript.Parser where 4 | 5 | import Language.RuScript.AST 6 | import Language.RuScript.Desugar 7 | 8 | import Text.ParserCombinators.Parsec 9 | import qualified Text.Parsec.Token as Tok 10 | 11 | testParse :: MyParser a -> String -> Either ParseError a 12 | testParse p = parse p "" 13 | 14 | parseProgram :: String -> Either ParseError Program 15 | parseProgram = parse pProgram "" 16 | 17 | -- testParser p = parse p "" 18 | 19 | type MyParser = CharParser () 20 | 21 | pProgram :: MyParser Program 22 | pProgram = Program <$> many (whiteSpace *> pTopLevel <* whiteSpace) 23 | 24 | 25 | pTopLevel :: MyParser (Either Statement Declaration) 26 | pTopLevel = try (Left <$> pStatement) 27 | <|> Right <$> (pImportDecl <|> pFuncDecl <|> pClassDecl) 28 | 29 | pImportDecl :: MyParser Declaration 30 | pImportDecl = ImportDecl <$> (reserved "import" *> whiteSpace *> (pIdent `sepEndBy` char '.')) 31 | 32 | pQualified :: MyParser Qualified 33 | pQualified = f <$> pIdent `sepEndBy` string "::" 34 | where 35 | f [] = error "parsed nothing out as qualified name" 36 | f xs = let xs' = reverse xs 37 | in Qualified (reverse $ tail xs') (head xs') 38 | 39 | pStatement :: MyParser Statement 40 | pStatement = SVar <$> (reserved "var" *> pBinding) <*> pMaybeEqualExpr <* char ';' <* whiteSpace 41 | <|> try (SAssign <$> (pLHS <* pEq) <*> (pExpr <* char ';') <* whiteSpace) 42 | <|> SReturn <$> (reserved "return" *> pExpr <* char ';' <* whiteSpace) 43 | <|> reserved "break" *> char ';' *> return SBreak <* whiteSpace 44 | <|> SBlock <$> (pBranch <|> pLoop) 45 | <|> try (SInvoke <$> (EVar <$> pIdent <* char '.') <*> pIdent <*> pParams <* char ';' <* whiteSpace) 46 | <|> try (SInvoke <$> (parens pExpr <* char '.') <*> pIdent <*> pParams <* char ';' <* whiteSpace) 47 | <|> SCall <$> pQualified <*> pParams <* char ';' <* whiteSpace 48 | 49 | pLHS :: MyParser LHS 50 | pLHS = try (LAttr <$> (pIdent <* char '.') <*> pIdent) 51 | <|> LVar <$> pIdent 52 | 53 | pMaybeEqualExpr :: MyParser (Maybe Expr) 54 | pMaybeEqualExpr = try (Just <$> (whiteSpace *> char '=' *> whiteSpace *> pExpr)) 55 | <|> return Nothing 56 | 57 | pBranch :: MyParser Block 58 | pBranch = do 59 | reserved "if" 60 | whiteSpace 61 | cond <- parens pExpr 62 | whiteSpace 63 | cb1 <- braces (many pStatement) 64 | whiteSpace 65 | reserved "else" 66 | cb2 <- braces (many pStatement) 67 | return $ Branch cond cb1 cb2 68 | 69 | pLoop :: MyParser Block 70 | pLoop = do 71 | reserved "while" 72 | whiteSpace 73 | cond <- parens pExpr 74 | whiteSpace 75 | cb <- braces (many pStatement) 76 | return $ Loop cond cb 77 | 78 | pClassDecl :: MyParser Declaration 79 | pClassDecl = do 80 | reserved "class" 81 | whiteSpace 82 | className <- pIdent 83 | whiteSpace 84 | mInherits <- try pInherit 85 | (as, ms) <- braces $ do 86 | as <- many $ try ((,) <$> pVisibility <*> pBinding) 87 | ms <- many ((,) <$> pVisibility <*> pMethod) 88 | return (as, ms) 89 | return $ ClassDecl (Qualified [] className) mInherits as ms 90 | 91 | pVisibility :: MyParser Visibility 92 | pVisibility = try (return Private <* reserved "private") 93 | <|> return Public 94 | 95 | pInherit :: MyParser (Maybe Qualified) 96 | pInherit = Just <$> (reserved "inherits" *> pQualified) 97 | <|> return Nothing 98 | 99 | pMethod :: MyParser Method 100 | pMethod = Virtual <$> (reserved "virtual" *> pFnSig) 101 | <|> Concrete <$> pFnSig <*> braces (many pStatement) 102 | 103 | pFuncDecl :: MyParser Declaration 104 | pFuncDecl = FnDecl <$> pFnSig <*> braces (many pStatement) 105 | 106 | pFnSig :: MyParser FnSig 107 | pFnSig = do 108 | reserved "fn" 109 | whiteSpace 110 | name <- pIdent 111 | whiteSpace 112 | args <- whiteSpace *> (parens $ pBinding `sepEndBy` (char ',' <* whiteSpace)) 113 | whiteSpace 114 | mty <- pMaybeRetType 115 | whiteSpace 116 | return $ FnSig (Qualified [] name) args mty 117 | 118 | pMaybeRetType :: MyParser (Maybe Type) 119 | pMaybeRetType = try (Just <$> (string "->" *> whiteSpace *> pType)) 120 | <|> return Nothing 121 | 122 | pBinding :: MyParser Binding 123 | pBinding = (,) <$> (pIdent <* whiteSpace) <*> (char ':' *> (whiteSpace *> pType)) 124 | 125 | pType :: MyParser Type 126 | pType = reserved "Int" *> return TyInt 127 | <|> reserved "Bool" *> return TyBool 128 | <|> reserved "Str" *> return TyStr 129 | <|> reserved "Nil" *> return TyNil 130 | <|> TyList <$> (string "List<" *> pType <* char '>') 131 | <|> TyClass <$> pQualified 132 | 133 | pExpr :: MyParser Expr 134 | pExpr = pNew 135 | <|> try (EInvoke <$> (EVar <$> pIdent <* char '.') <*> pIdent <*> pParams) 136 | <|> try (EInvoke <$> (parens pExpr <* char '.') <*> pIdent <*> pParams) 137 | <|> try pGet 138 | <|> ELit <$> pLit 139 | <|> try (ECall <$> pQualified <*> pParams) 140 | <|> EVar <$> pIdent 141 | <|> desugarTerm <$> parens pTerm 142 | 143 | pBinary :: (Expr -> Expr -> Term) -> MyParser String -> MyParser Term 144 | pBinary cons pOp = cons <$> pExpr <* (whiteSpace *> pOp <* whiteSpace) <*> pExpr 145 | 146 | pTerm = try (pBinary TPlus (string "+")) 147 | <|> try (pBinary TLE (string "<=")) 148 | 149 | pNew :: MyParser Expr 150 | pNew = ENew <$> (reserved "new" *> pQualified) <*> pParams 151 | 152 | pParams :: MyParser [Expr] 153 | pParams = parens (pExpr `sepEndBy` (char ',' <* whiteSpace)) 154 | 155 | pGet :: MyParser Expr 156 | pGet = EGet <$> (pIdent <* char '.') <*> pIdent 157 | 158 | pLit :: MyParser Literal 159 | pLit = try (LBool <$> pBool) 160 | <|> LStr <$> parseString 161 | <|> LInt . fromInteger <$> pInt 162 | <|> string "[]" *> return LList 163 | <|> reserved "nil" *> return LNil 164 | 165 | pBool :: MyParser Bool 166 | pBool = reserved "True" *> return True 167 | <|> reserved "False" *> return False 168 | 169 | --------- LangDef ----------- 170 | 171 | pEq :: MyParser () 172 | pEq = whiteSpace *> reservedOp "=" 173 | 174 | langDef :: Tok.LanguageDef () 175 | langDef = Tok.LanguageDef { 176 | Tok.commentStart = "#-" 177 | , Tok.commentEnd = "-#" 178 | , Tok.commentLine = "#" 179 | , Tok.nestedComments = True 180 | , Tok.identStart = letter 181 | , Tok.identLetter = alphaNum <|> oneOf "_'" 182 | , Tok.opStart = oneOf ":!#$%&*+./<=>?@\\^|-~" 183 | , Tok.opLetter = oneOf ":!#$%&*+./<=>?@\\^|-~" 184 | , Tok.reservedNames = ["class", "fn", "new", "return", "break", "while", "if", "else", "private", "virtual", "var"] 185 | , Tok.reservedOpNames = ["="] 186 | , Tok.caseSensitive = True 187 | } 188 | 189 | 190 | lexer = Tok.makeTokenParser langDef 191 | 192 | parens = Tok.parens lexer 193 | braces = Tok.braces lexer 194 | reserved = Tok.reserved lexer 195 | reservedOp = Tok.reservedOp lexer 196 | whiteSpace = Tok.whiteSpace lexer 197 | pIdent = Tok.identifier lexer 198 | pInt = Tok.integer lexer 199 | 200 | escape :: Parser String 201 | escape = do 202 | d <- char '\\' 203 | c <- oneOf "\\\"0nrvtbf" -- all the characters which can be escaped 204 | return [d, c] 205 | 206 | nonEscape :: Parser Char 207 | nonEscape = noneOf "\\\"\0\n\r\v\t\b\f" 208 | 209 | character :: Parser String 210 | character = fmap return nonEscape <|> escape 211 | 212 | parseString :: Parser String 213 | parseString = do 214 | char '"' 215 | strings <- many character 216 | char '"' 217 | return $ concat strings 218 | 219 | -------------------------------------------------------------------------------- /rusc/src/Language/RuScript/Serialize.hs: -------------------------------------------------------------------------------- 1 | module Language.RuScript.Serialize where 2 | 3 | import Language.RuScript.ByteCode 4 | 5 | -- import Data.Word (Word8, Word32) 6 | import Data.Binary 7 | import Control.Monad 8 | import Data.ByteString.UTF8 (fromString) 9 | import Data.ByteString (unpack) 10 | import Data.List.Split (chunksOf) 11 | import Data.Bits (shiftL) 12 | 13 | data Segment = Segment Word8 [Word32] deriving Show 14 | 15 | -- 32 bit per operand 16 | inBytes :: Segment -> Word8 17 | inBytes (Segment _ operands) = fromIntegral (1 + length operands * 4) 18 | 19 | instance Binary Segment where 20 | put seg@(Segment opcode operands) = do 21 | put $ inBytes seg -- One byte: length of following payload in bytes 22 | put opcode -- One byte: opcode 23 | forM_ operands put -- 4 * number of operands bytes 24 | 25 | get = do 26 | bytes <- get :: Get Word8 27 | opcode <- get :: Get Word8 28 | operands <- mapM id $ take ((fromIntegral bytes - 1) `div` 4) $ repeat (get :: Get Word32) 29 | return $ Segment opcode operands 30 | 31 | serialize :: ByteCode -> Segment 32 | serialize (CALL i) = segify 0 [i] 33 | serialize (INVOKE s) = Segment (fromIntegral 1) $ strToWord32Arr s 34 | serialize RET = segify 2 [] 35 | serialize (JUMP (Left i)) = segify 3 [i] 36 | serialize (JUMPT (Left i)) = segify 4 [i] 37 | serialize (JUMPF (Left i)) = segify 5 [i] 38 | serialize (PUSH i) = segify 6 [i] 39 | serialize (POP i) = segify 7 [i] 40 | serialize (NEW i) = segify 8 [i] 41 | serialize (PUSHA s) = Segment (fromIntegral 9) $ strToWord32Arr s 42 | serialize (POPA s) = Segment (fromIntegral 10) $ strToWord32Arr s 43 | serialize (PUSHSTR s) = Segment (fromIntegral 11) $ strToWord32Arr s 44 | serialize (PUSHINT i) = segify 12 [i] 45 | serialize (PUSHBOOL i) = segify 13 [i] 46 | serialize (CLASS i1 i2 i3) = segify 14 [i1, i2, i3] 47 | serialize SFUNC = segify 15 [] 48 | serialize (EBODY i) = segify 16 [i] 49 | serialize PUSHLIST = segify 17 [] 50 | serialize PUSHNIL = segify 18 [] 51 | serialize other = error $ "can't serialize" ++ show other 52 | 53 | -- serialize x = error $ "unimplelemented serialization of: " ++ show x 54 | 55 | segify :: Int -> [Int] -> Segment 56 | segify i is = Segment (fromIntegral i) (map fromIntegral is) 57 | 58 | -- UTF-8 encoding 59 | strToWord32Arr :: String -> [Word32] 60 | strToWord32Arr str = map (f . reverse) . chunksOf 4 $ unpack (fromString str) 61 | where 62 | f :: [Word8] -> Word32 63 | f [] = 0 64 | f (l:hs) = fromIntegral l + f hs `shiftL` 8 65 | 66 | -------------------------------------------------------------------------------- /rusc/src/Language/RuScript/StaticCheck.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE TemplateHaskell, RankNTypes, 2 | TypeSynonymInstances, FlexibleInstances, 3 | FlexibleContexts, LambdaCase #-} 4 | 5 | module Language.RuScript.StaticCheck where 6 | 7 | {- 8 | Static checking of source code 9 | 10 | 1. Type check 11 | 2. Class inheritance (including virtual method) 12 | 3. Visibility 13 | 14 | However, there should be some generic functions. 15 | -} 16 | 17 | import Language.RuScript.AST 18 | 19 | import qualified Data.Map as M 20 | import Control.Monad.Except 21 | import Control.Monad.State 22 | import Control.Lens 23 | import Data.Either 24 | import qualified Data.List as L 25 | 26 | data StaticState = StaticState { 27 | _localTable :: M.Map Name Type 28 | , _funcTable :: M.Map Qualified FnSig 29 | , _classTable :: M.Map Qualified ClassType 30 | , _enclosedRet :: Maybe Type 31 | } 32 | 33 | data ClassType = ClassType { 34 | _mInherit :: Maybe Qualified 35 | , _attrTable :: M.Map Name (Visibility, Type) 36 | , _mtdTable :: M.Map Name (Visibility, FnSig) 37 | } 38 | 39 | initStaticState :: StaticState 40 | initStaticState = StaticState M.empty M.empty M.empty Nothing 41 | 42 | makeLenses ''StaticState 43 | makeLenses ''ClassType 44 | 45 | type Static = ExceptT String (State StaticState) 46 | 47 | checkProgram :: Program -> Either String () 48 | checkProgram prog = evalState (runExceptT (check prog)) initStaticState 49 | 50 | class Check a where 51 | check :: a -> Static () 52 | 53 | instance Check a => Check [a] where 54 | check = mapM_ check 55 | 56 | instance Check Program where 57 | check (Program mix) = do 58 | let stmts = lefts mix 59 | let decls = rights mix 60 | -- note the order: we must load the information in 61 | -- declarations first 62 | check decls 63 | check stmts 64 | 65 | instance Check Declaration where 66 | check (FnDecl sig stmts) = do 67 | addFnSig sig 68 | inFunc sig $ check stmts 69 | 70 | check (ClassDecl name mFather attrs methods) = do 71 | addEmptyClass name mFather 72 | mapM_ (\(vis, binding) -> addAttr name vis binding) attrs 73 | mapM_ (\(vis, method) -> addMethod name vis method) methods 74 | 75 | check (ImportDecl _) = return () 76 | 77 | instance Check Statement where 78 | check (SVar binding mExpr) = do 79 | addBinding binding 80 | case mExpr of 81 | Just expr -> do 82 | ty <- infer expr 83 | snd binding `isSubTypeOf` ty 84 | Nothing -> return () 85 | 86 | check (SAssign lhs expr) = do 87 | ty1 <- infer lhs 88 | ty2 <- infer expr 89 | ty1 `isSubTypeOf` ty2 90 | 91 | check (SBlock block) = check block 92 | 93 | check (SInvoke recv method params) = do 94 | ty <- infer recv 95 | sig@(FnSig _ _ Nothing) <- getSigFromType method ty 96 | checkFunc sig params 97 | 98 | 99 | check (SCall f params) = do 100 | sig@(FnSig _ _ Nothing) <- getSigOfFunc f 101 | checkFunc sig params 102 | 103 | check (SReturn expr) = do 104 | ty <- getEncloseFuncRetTy 105 | ty' <- infer expr 106 | ty `isSubTypeOf` ty' 107 | 108 | check SBreak = return () 109 | 110 | checkFunc :: FnSig -> [Expr] -> Static () 111 | checkFunc (FnSig _ bindings _) params = do 112 | tys <- mapM infer params 113 | assert (length tys == length bindings) "length of params is not equal to type signature" 114 | mapM_ (uncurry isSubTypeOf) $ zip (map snd bindings) tys 115 | 116 | instance Check Block where 117 | check (Branch expr ss1 ss2) = do 118 | ty <- infer expr 119 | assert (ty == TyBool) "Type of 'if' condition is not Bool" 120 | check ss1 121 | check ss2 122 | 123 | check (Loop expr ss) = do 124 | ty <- infer expr 125 | assert (ty == TyBool) "Type of 'while' condition is not Bool" 126 | check ss 127 | 128 | class Infer a where 129 | infer :: a -> Static Type 130 | 131 | instance Infer Expr where 132 | infer (EVar x) = lookUpLocalVar x 133 | infer (EGet x attr) = do 134 | ty <- infer x 135 | checkVis x ty attr 136 | snd <$> lookUpAttr ty attr 137 | 138 | infer (EInvoke x f params) = do 139 | ty <- infer x 140 | sig@(FnSig _ _ (Just retty)) <- getSigFromType f ty 141 | checkFunc sig params 142 | return retty 143 | 144 | infer (ECall f params) = do 145 | sig@(FnSig _ _ (Just ty)) <- getSigOfFunc f 146 | checkFunc sig params 147 | return ty 148 | 149 | infer (ENew cls params) = do 150 | attrs <- getAttrs cls 151 | tys <- mapM infer params 152 | mapM_ (uncurry isSubTypeOf) $ zip (map (snd . snd) attrs) tys 153 | return $ TyClass cls 154 | 155 | infer (ELit lit) = case lit of 156 | LStr _ -> return TyStr 157 | LInt _ -> return TyInt 158 | LBool _ -> return TyBool 159 | LList -> return $ TyList TyBot 160 | LNil -> return TyNil 161 | 162 | instance Infer Name where 163 | infer = lookUpLocalVar 164 | 165 | instance Infer LHS where 166 | infer (LVar x) = lookUpLocalVar x 167 | infer (LAttr x attr) = do 168 | ty <- infer x 169 | checkVis x ty attr 170 | snd <$> lookUpAttr ty attr 171 | 172 | 173 | -- Check visibility rule: If `x` is `this`, no check; 174 | -- else, check by querying the class table 175 | checkVis :: Name -> Type -> Name -> Static () 176 | checkVis "this" _ _ = return () 177 | checkVis _ ty attrName = do 178 | (vis, _) <- lookUpAttr ty attrName 179 | case vis of 180 | Public -> return () 181 | Private -> throwError $ "accessing private attribute " ++ attrName 182 | 183 | addFnSig :: FnSig -> Static () 184 | addFnSig sig@(FnSig name _ _) = funcTable %= M.insert name sig 185 | 186 | 187 | inMethod :: Qualified -> FnSig -> Static a -> Static a 188 | inMethod cls (FnSig _ bindings mRet) m = do 189 | case mRet of 190 | Just ty -> enclosedRet .= Just ty 191 | Nothing -> return () 192 | 193 | old <- use localTable 194 | localTable .= M.fromList (bindings ++ [("self", TyClass cls)]) 195 | 196 | ret <- m 197 | 198 | enclosedRet .= Nothing 199 | localTable .= old 200 | return ret 201 | 202 | 203 | inFunc :: FnSig -> Static a -> Static a 204 | inFunc (FnSig _ bindings mRet) m = do 205 | case mRet of 206 | Just ty -> enclosedRet .= Just ty 207 | Nothing -> return () 208 | 209 | old <- use localTable 210 | localTable .= M.fromList bindings 211 | ret <- m 212 | 213 | enclosedRet .= Nothing 214 | localTable .= old 215 | return ret 216 | 217 | 218 | addEmptyClass :: Qualified -> (Maybe Qualified) -> Static () 219 | addEmptyClass name mFather = 220 | classTable %= M.insert name (ClassType mFather M.empty M.empty) 221 | 222 | addAttr :: Qualified -> Visibility -> Binding -> Static () 223 | addAttr name vis (x, ty) = 224 | classTable %= M.update (Just . over attrTable (M.insert x (vis, ty))) name 225 | 226 | addMethod :: Qualified -> Visibility -> Method -> Static () 227 | addMethod name vis = \case 228 | Virtual sig -> addMethod' sig 229 | Concrete sig stmts -> do 230 | addMethod' sig 231 | inMethod name sig $ check stmts 232 | where 233 | addMethod' :: FnSig -> Static () 234 | addMethod' sig@(FnSig (Qualified _ x) _ _) = 235 | classTable %= M.update (Just . over mtdTable (M.insert x (vis, sig))) name 236 | 237 | -- isSubTypeOf 238 | isSubTypeOf :: Type -> Type -> Static () 239 | isSubTypeOf t1 t2 240 | | t1 == t2 = return () 241 | | otherwise = 242 | case t2 of 243 | TyClass cls -> do 244 | t <- lookupClass cls 245 | case (_mInherit t) of 246 | Just inherit 247 | | TyClass inherit == t1 -> return () 248 | | otherwise -> isSubTypeOf t1 (TyClass inherit) 249 | Nothing -> err 250 | TyList TyBot -> 251 | case t1 of 252 | TyList _ -> return () 253 | _ -> err 254 | _ -> err 255 | where 256 | err = throwError $ show t2 ++ " can't be subtype of " ++ show t1 257 | 258 | 259 | assertEq :: (MonadError String m, Eq a, Show a) => a -> a -> m () 260 | assertEq a b = assert (a == b) $ show a ++ " and " ++ show b ++ " are not equal" 261 | 262 | assert :: MonadError e m => Bool -> e -> m () 263 | assert b e = if b then return () else throwError e 264 | 265 | addBinding :: Binding -> Static () 266 | addBinding (x, ty) = localTable %= M.insert x ty 267 | 268 | 269 | getSigFromType :: Name -> Type -> Static FnSig 270 | getSigFromType method = \case 271 | TyClass cls -> do 272 | table <- lookupClass cls 273 | case M.lookup method $ _mtdTable table of 274 | Just (_, sig) -> return sig 275 | Nothing -> throwError $ "has no idea of method " ++ method 276 | other -> queryBuiltinMethod method other 277 | 278 | lookupClass :: Qualified -> Static ClassType 279 | lookupClass cls = do 280 | t <- use classTable 281 | case M.lookup cls t of 282 | Just table -> return table 283 | Nothing -> throwError $ "I has no idea of class " ++ show cls 284 | 285 | 286 | getSigOfFunc :: Qualified -> Static FnSig 287 | getSigOfFunc f = do 288 | t <- use funcTable 289 | case M.lookup f t of 290 | Just sig -> return sig 291 | Nothing -> throwError $ "can't find signature of " ++ show f 292 | 293 | getEncloseFuncRetTy :: Static Type 294 | getEncloseFuncRetTy = do 295 | e <- use enclosedRet 296 | case e of 297 | Just retty -> return retty 298 | Nothing -> throwError "can't find return type of enclosed function environment" 299 | 300 | lookUpLocalVar :: Name -> Static Type 301 | lookUpLocalVar x = do 302 | t <- use localTable 303 | case M.lookup x t of 304 | Just ty -> return ty 305 | Nothing -> throwError $ "can't find type of variable " ++ x 306 | 307 | lookUpAttr :: Type -> Name -> Static (Visibility, Type) 308 | lookUpAttr ty name = do 309 | case ty of 310 | TyClass cls -> do 311 | bindings <- getAttrs cls 312 | case L.lookup name bindings of 313 | Just attr -> return attr 314 | Nothing -> throwError $ "can't find type of attribute " ++ name ++ " of class " ++ show cls 315 | other -> (,) Public <$> queryBuiltinAttr name other 316 | 317 | getAttrs :: Qualified -> Static [(Name, (Visibility, Type))] 318 | getAttrs cls = do 319 | t <- use classTable 320 | case M.lookup cls t of 321 | Just (ClassType _ attrs _) -> return $ M.toList attrs 322 | _ -> throwError $ "can't get attributes of class " ++ show cls 323 | 324 | builtInMethods :: M.Map (Type, Name) FnSig 325 | builtInMethods = M.fromList [ 326 | ((TyInt, "add"), FnSig (Qualified [] "add") [("x", TyInt)] (Just TyInt)) 327 | , ((TyInt, "print"), printSig) 328 | , ((TyBool, "not"), FnSig (Qualified [] "not") [] Nothing) 329 | , ((TyInt, "le"), FnSig (Qualified [] "le") [("x", TyInt)] (Just TyBool)) 330 | , ((TyStr, "print"), printSig) 331 | , ((TyNil, "print"), printSig) 332 | ] 333 | where 334 | printSig = FnSig (Qualified [] "print") [] Nothing 335 | 336 | builtInAttrs :: M.Map (Type, Name) Type 337 | builtInAttrs = M.empty 338 | 339 | queryBuiltinMethod :: Name -> Type -> Static FnSig 340 | queryBuiltinMethod name ty = 341 | case M.lookup (ty, name) builtInMethods of 342 | Just sig -> return sig 343 | Nothing -> 344 | case ty of 345 | TyList eTy -> case name of 346 | "cons" -> return $ FnSig (Qualified [] "cons") [("x", eTy)] (Just (TyList eTy)) 347 | "print" -> return $ FnSig (Qualified [] "print") [] Nothing 348 | _ -> err 349 | _ -> err 350 | where 351 | err = throwError $ show ty ++ " doesn't have \"" ++ name ++ "\" method builtin" 352 | 353 | 354 | queryBuiltinAttr :: Name -> Type -> Static Type 355 | queryBuiltinAttr name ty = 356 | case M.lookup (ty, name) builtInAttrs of 357 | Just attrty -> return attrty 358 | Nothing -> throwError $ show ty ++ " doesn't have \"" ++ name ++ "\" method builtin" 359 | 360 | -------------------------------------------------------------------------------- /rusc/src/Language/RuScript/Traversal.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE TemplateHaskell, RankNTypes #-} 2 | -- Construct traversal utilities by Multiplate 3 | 4 | module Language.RuScript.Traversal where 5 | 6 | import MultiplateDerive 7 | import Language.RuScript.AST 8 | 9 | import Data.Generics.Multiplate 10 | import Data.Functor.Constant 11 | import Data.Functor.Identity 12 | 13 | 14 | temPlate "Plate" [ "Qualified", "Type", "Block", "Expr", "Literal", "Statement", "LHS", "FnSig" ] 15 | 16 | mapper :: Plate Identity -> (forall f. Plate f -> a -> f a) -> a -> a 17 | mapper plate selector = traverseFor selector (mapFamily plate) 18 | -------------------------------------------------------------------------------- /rusc/src/Language/RuScript/optimize.v: -------------------------------------------------------------------------------- 1 | Extraction Language Haskell. 2 | 3 | Require Import Coq.Unicode.Utf8. 4 | (* Require Import ZArith_base. *) 5 | 6 | Inductive ByteCode (z: Set) := 7 | | PUSH : z → ByteCode 8 | | POP : z → ByteCode 9 | . 10 | 11 | Extraction "Optimize'.hs" ByteCode. 12 | 13 | -------------------------------------------------------------------------------- /rusc/src/Main.hs: -------------------------------------------------------------------------------- 1 | import System.IO hiding (writeFile) 2 | import System.Environment 3 | import Prelude hiding (writeFile, concat) 4 | import Data.ByteString.Lazy (writeFile, concat) 5 | import Data.Binary (encode) 6 | import Control.Monad (when) 7 | import System.Exit (exitFailure) 8 | import System.FilePath (()) 9 | 10 | import Language.RuScript.Serialize 11 | import Language.RuScript.Codegen 12 | import Language.RuScript.Parser 13 | import Language.RuScript.StaticCheck 14 | import Language.RuScript.Option 15 | import Language.RuScript.Optimize 16 | import Language.RuScript.Import 17 | 18 | main :: IO () 19 | main = do 20 | hSetBuffering stdout NoBuffering 21 | args <- getArgs 22 | if (length args > 1) then do 23 | let opt = parseOpt $ drop 2 args 24 | let includeDir = _includeOpt opt 25 | txt <- readFile (includeDir head args) 26 | let target = includeDir (args !! 1) 27 | case parseProgram txt of 28 | Left err -> exitError $ "Error in parsing: " ++ show err 29 | Right program -> do 30 | program' <- resolveDeps includeDir program 31 | when (_debugOpt opt) $ print program' 32 | case checkProgram program' of 33 | Left err -> exitError $ "Error in checking: " ++ err 34 | Right _ -> do 35 | let bytecode = if (_optimizeOpt opt) 36 | then optimize $ runCodegen program' 37 | else runCodegen program' 38 | when (_debugOpt opt) $ printCode bytecode 39 | writeFile target (concat $ map (encode . serialize) bytecode) 40 | else putStrLn "usage: rusc " 41 | 42 | where 43 | exitError s = hPutStrLn stderr s >> exitFailure 44 | -------------------------------------------------------------------------------- /rusc/src/MultiplateDerive.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE TemplateHaskell #-} 2 | {-# LANGUAGE LambdaCase #-} 3 | 4 | module MultiplateDerive where 5 | 6 | import Language.Haskell.TH hiding (varE) 7 | import Data.Char (toLower) 8 | import Control.Monad (forM) 9 | 10 | temPlate :: String -> [String] -> Q [Dec] 11 | temPlate plateName tyNames = do 12 | decls <- forM tyNames $ \tyName -> do 13 | Just ty <- lookupTypeName tyName 14 | reify ty >>= \case 15 | TyConI (DataD _ _ _ _ constrs _) -> return (tyName, constrs) 16 | other -> error $ "Can't read data type declaration: " ++ show other 17 | 18 | clauses <- mapM (genInstance (extractNames decls)) decls 19 | 20 | let mkDecl = FunD (mkName "mkPlate") 21 | [Clause [VarP $ mkName "build"] 22 | (NormalB $ foldl AppE (ConE (mkName plateName)) 23 | $ map (\n -> AppE (VarE $ mkName "build") 24 | (VarE $ mkName (lowerHead n ++ "_"))) 25 | tyNames) 26 | []] 27 | let instDecl = InstanceD Nothing [] 28 | (AppT (ConT $ mkName "Multiplate") (ConT $ mkName plateName)) 29 | [FunD (mkName "multiplate") 30 | [Clause [VarP $ mkName "child"] 31 | (NormalB $ foldl AppE (ConE (mkName plateName)) 32 | $ map (\n -> VarE . mkName $ "build" ++ n) 33 | tyNames) 34 | clauses] 35 | , mkDecl] 36 | 37 | return $ [ platDecl, instDecl ] 38 | where 39 | platDecl = DataD [] (mkName plateName) [binder] Nothing [constr] [] 40 | binder = PlainTV (mkName "f") 41 | constr = RecC (mkName plateName) $ map (\s -> (mkName (lowerHead s ++ "_"), 42 | Bang NoSourceUnpackedness NoSourceStrictness, 43 | toType s)) tyNames 44 | toType s = let ty = ConT $ mkName s 45 | in AppT (AppT ArrowT ty) (AppT (VarT (mkName "f")) ty) 46 | extractNames = map fst 47 | 48 | genInstance :: [String] -> (String, [Con]) -> Q Dec 49 | genInstance recTyNames (tyName, constrs) = do 50 | let funName = mkName $ "build" ++ tyName 51 | clauses <- forM constrs $ \case 52 | NormalC name tys -> genClause recTyNames name tys snd 53 | RecC name tys -> genClause recTyNames name tys (\(_, _, ty) -> ty) 54 | other -> error $ "genInstance bad constr: " ++ show other 55 | return $ FunD funName clauses 56 | 57 | genClause :: [String] -> Name -> [a] -> (a -> Type) -> Q Clause 58 | genClause recTyNames name tys tyNameSel = do 59 | let conName = mkName $ nameBase name 60 | let numTys = length tys 61 | newNames <- sequence $ take numTys $ repeat (newName "x") :: Q [Name] 62 | let pairs = zip newNames $ map tyNameSel tys 63 | segments <- mapM (\(v, ty) -> build recTyNames (\f -> f (VarE v)) ty) pairs 64 | let bodys = NormalB $ appChain (ConE name) segments 65 | return $ Clause [ConP conName (map VarP newNames)] 66 | bodys 67 | [] 68 | 69 | 70 | ---- Core 71 | 72 | build :: [String] -> ((Exp -> Exp) -> Exp) -> Type -> Q Exp 73 | -- :: , return expr s.t. :: Applicative f => f 74 | build astTys withDestr ty = case ty of 75 | -- :: a 76 | ConT name -> do 77 | let baseName = nameBase name 78 | if baseName `elem` astTys 79 | then return $ buildE (selectorE baseName) (withDestr id) 80 | -- build :: f a 81 | else return $ pureE (withDestr id) 82 | -- pure :: f a 83 | 84 | AppT t ty -> do 85 | case getTuple t of 86 | Just depth -> do 87 | -- :: (t1, t2, ..., t_{depth}), ~ t_{depth} 88 | let tupleConE = getTupleConE depth -- 2: (,) ... 89 | let ts = getRTs t ++ [ty] -- [Type], length = depth 90 | let components = map withDestr $ map (AppE . getSelE) [1..depth] 91 | es <- mapM (\(t, e) -> build astTys (\f -> f e) t) $ zip ts components 92 | return $ appChain tupleConE es -- :: f (t1, ..., t_{depth}) 93 | Nothing -> do 94 | -- :: Traversable t => t a 95 | x <- newName "x" 96 | f <- lamE x <$> build astTys (\f -> f (VarE x)) ty -- :: a -> f a 97 | let g = wrapE f -- :: t a -> f (t a) 98 | return $ AppE g (withDestr id) -- :: f (t a) 99 | 100 | other -> error $ "illegal: " ++ show other 101 | 102 | where -- Helpers, a lot 103 | constrE :: String -> Exp -- lowerHead 104 | constrE = ConE . mkName 105 | selectorE :: String -> Exp 106 | selectorE s = varE (lowerHead s ++ "_") 107 | getTuple :: Type -> Maybe Int 108 | getTuple (TupleT i) = Just i 109 | getTuple (AppT f _) = getTuple f 110 | getTuple _ = Nothing 111 | getTupleConE :: Int -> Exp 112 | getTupleConE i = constrE $ "(" ++ take (i - 1) (repeat ',') ++ ")" 113 | getRTs :: Type -> [Type] 114 | getRTs (AppT l r) = getRTs l ++ [r] 115 | getRTs _ = [] 116 | getSelE :: Int -> Exp -- Data.Tuple.Select 117 | getSelE i = varE $ "sel" ++ show i 118 | wrapE :: Exp -> Exp 119 | wrapE f = InfixE (Just $ varE "sequenceA") (varE ".") (Just $ AppE (varE "fmap") f) 120 | lamE :: Name -> Exp -> Exp 121 | lamE x e = LamE [VarP x] e 122 | 123 | lowerHead :: String -> String 124 | lowerHead "" = "" 125 | lowerHead (s:ss) = toLower s : ss 126 | 127 | 128 | appChain :: Exp -> [Exp] -> Exp 129 | appChain conE [] = pureE conE 130 | appChain conE (fd:fds) = 131 | let header = InfixE (Just conE) (varE "<$>") (Just fd) 132 | in foldl (\e1 e2 -> InfixE (Just e1) (varE "<*>") (Just e2)) header fds 133 | 134 | buildE :: Exp -> Exp -> Exp 135 | buildE constrE e = AppE (AppE constrE (varE "child")) e 136 | 137 | pureE :: Exp -> Exp 138 | pureE e = AppE (varE "pure") e 139 | 140 | varE :: String -> Exp 141 | varE = VarE . mkName 142 | 143 | -------------------------------------------------------------------------------- /rusc/stack.yaml: -------------------------------------------------------------------------------- 1 | # For more information, see: https://github.com/commercialhaskell/stack/blob/master/doc/yaml_configuration.md 2 | 3 | # Specifies the GHC version and set of packages available (e.g., lts-3.5, nightly-2015-09-21, ghc-7.10.2) 4 | resolver: lts-7.3 5 | 6 | # Local packages, usually specified by relative directory name 7 | packages: 8 | - '.' 9 | - '../multiplate' 10 | 11 | # Packages to be pulled from upstream that are not in the resolver (e.g., acme-missiles-0.3) 12 | extra-deps: 13 | - multiplate-0.0.4 14 | 15 | # Override default flag values for local packages and extra-deps 16 | flags: {} 17 | 18 | # Control whether we use the GHC we find on the path 19 | # system-ghc: true 20 | 21 | # Require a specific version of stack, using version ranges 22 | # require-stack-version: -any # Default 23 | # require-stack-version: >= 0.1.4.0 24 | 25 | # Override the architecture used by stack, especially useful on Windows 26 | # arch: i386 27 | # arch: x86_64 28 | 29 | # Extra directories used by stack for building 30 | # extra-include-dirs: [/path/to/dir] 31 | # extra-lib-dirs: [/path/to/dir] 32 | -------------------------------------------------------------------------------- /rvm/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rvm" 3 | version = "0.3.2" 4 | authors = ["Zhen Zhang "] 5 | 6 | [lib] 7 | name = "rvm" 8 | path = "src/lib.rs" 9 | 10 | [[bin]] 11 | name = "main" 12 | path = "src/main.rs" 13 | 14 | [dependencies.gc] 15 | path = "../rust-gc/gc" 16 | version = "0.2.1" 17 | 18 | [dependencies.gc_derive] 19 | path = "../rust-gc/gc_derive" 20 | version = "0.2.1" 21 | -------------------------------------------------------------------------------- /rvm/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015 Zhen Zhang 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included 12 | in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 17 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 18 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 19 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 20 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /rvm/src/bytecode.rs: -------------------------------------------------------------------------------- 1 | //! 2 | //! Bytecode Instructions 3 | //! 4 | 5 | use super::*; 6 | 7 | #[derive(Clone, Debug)] 8 | pub enum ByteCode { 9 | 10 | // --- Frame Manipulation --- 11 | 12 | /// Call declared function: arguments number, function index 13 | CALL(Integer), 14 | 15 | /// Invoke method of TOS: arguments number, method name 16 | INVOKE(String), 17 | 18 | /// Return without value 19 | RET, 20 | 21 | // ---- Control ----- 22 | 23 | /// Jump unconditionally: relative offset 24 | JUMP(Integer), 25 | 26 | /// Jump if TOS is `True` boolean: relative offset 27 | JUMPT(Integer), 28 | 29 | /// Jump if TOS is `False` boolean: relative offset 30 | JUMPF(Integer), 31 | 32 | // --- General Data Stack Manipulation --- 33 | 34 | /// Push local variable on stack: variable index 35 | PUSH(Integer), 36 | 37 | /// Pop TOS to local variable: variable index 38 | POP(Integer), 39 | 40 | // --- OO Specific --- 41 | 42 | /// Instantiate a class and push instance on stack: class index 43 | NEW(Integer), 44 | 45 | /// Push attribute of TOS on stack: attribute name 46 | PUSHA(String), 47 | 48 | /// Pop TOS to certain attribute of NTOS, 49 | /// the mutated NTOS will be pushed back: attribute name 50 | POPA(String), 51 | 52 | // --- Built-in ---- 53 | 54 | /// Push a literal String object on stack 55 | PUSHSTR(String), 56 | 57 | /// Push a literal Integer object on stack 58 | PUSHINT(Integer), 59 | 60 | /// Push a literal Boolean object on stack, zero is False, non-zero is True 61 | PUSHBOOL(Integer), 62 | 63 | // ---- Pseudo Instruction --- 64 | 65 | /// Class declaration: Number of attributes, number of methods, 66 | /// father class index (negative for none) 67 | CLASS(Integer, Integer, Integer), 68 | 69 | /// Mark the start of function declaration 70 | SFUNC, 71 | 72 | /// Mark the end of function/method definition: Number of local vars 73 | EBODY(Integer), 74 | 75 | /// Push an empty array on stack 76 | PUSHLIST, 77 | 78 | /// Push Nil on stack 79 | PUSHNIL 80 | } 81 | 82 | -------------------------------------------------------------------------------- /rvm/src/class.rs: -------------------------------------------------------------------------------- 1 | //! 2 | //! Class structure 3 | //! 4 | //! The class is mostly used immutable, i.e., once parsed 5 | //! from the bytecode stream, it will never change. The 6 | //! inheritance is maintained in this structure as well. 7 | //! Code resue is down by querying bottom up, which might 8 | //! not be very efficient, but simpler 9 | 10 | use std::collections::HashMap; 11 | use bytecode::ByteCode; 12 | use bytecode::ByteCode::*; 13 | use function::*; 14 | use super::*; 15 | use env::Env; 16 | 17 | pub struct Class { 18 | pub father : Option, 19 | pub methods : HashMap, 20 | pub attrs : Vec, 21 | } 22 | 23 | impl Class { 24 | /// Get a function structure of method by name 25 | pub fn get_method<'a>(&'a self, name: &String, env: &'a Env) -> Option<&'a Function> { 26 | match self.methods.get(name) { 27 | Some(method) => Some(method), 28 | None => { 29 | match self.father { 30 | Some(idx) => env.classes[idx].get_method(name, env), 31 | None => panic!("Can' find method {:?}", name), 32 | } 33 | } 34 | } 35 | } 36 | } 37 | 38 | /// Parse class from bytecode stream 39 | pub fn parse_class(code: &Vec, n_attrs: usize, 40 | n_methods: usize, father_idx: Integer, pc: &mut usize) -> Class { 41 | 42 | let mut attrs = Vec::new(); 43 | let mut methods = HashMap::new(); 44 | 45 | // Collect the attributes' names 46 | for _ in 0..n_attrs { 47 | match code[*pc] { 48 | PUSHSTR(ref s) => { 49 | attrs.push(s.clone()); 50 | *pc = *pc + 1; 51 | }, 52 | _ => { 53 | *pc = *pc + 1; 54 | break; 55 | } 56 | } 57 | } 58 | 59 | for _ in 0..n_methods { 60 | let function = parse_function(code, pc); 61 | 62 | // Collect the method name 63 | match code[*pc] { 64 | PUSHSTR(ref s) => { methods.insert(s.clone(), function); }, 65 | _ => panic!("End of method function must be followed with PUSHSTR"), 66 | } 67 | 68 | *pc = *pc + 1; 69 | } 70 | 71 | // father_idx < 0 means no inheritance 72 | let mut father = None; 73 | if father_idx >= 0 { 74 | father = Some(father_idx as usize); 75 | } 76 | 77 | Class { 78 | father : father, 79 | methods : methods, 80 | attrs : attrs, 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /rvm/src/cmdopt.rs: -------------------------------------------------------------------------------- 1 | //! 2 | //! Command line Option 3 | //! 4 | 5 | use std::env; 6 | 7 | #[derive(Clone)] 8 | pub struct CmdOpt { 9 | /// Dump debug information 10 | pub debug : bool, 11 | /// Dump GC profile information 12 | pub gc_dump : bool, 13 | /// Benchmark the program by repeating 14 | pub bench : Option, 15 | } 16 | 17 | /// Default command line options 18 | pub fn default_cmdopt() -> CmdOpt { 19 | CmdOpt { 20 | debug : false, 21 | gc_dump : false, 22 | bench : None, 23 | } 24 | } 25 | 26 | pub fn parse_opt() -> CmdOpt { 27 | let mut i = 2; 28 | let mut opt = default_cmdopt(); 29 | let nargs = env::args().count(); 30 | 31 | while i < nargs { 32 | match env::args().nth(i) { 33 | Some(ref opt_str) => { 34 | if opt_str == "-debug" { 35 | opt.debug = true; 36 | } else if opt_str == "-bench" && i + 1 < nargs { 37 | let s = env::args().nth(i + 1).unwrap(); 38 | opt.bench = Some(s.parse::().unwrap()); 39 | i = i + 1; 40 | } else if opt_str == "-gc_dump" { 41 | opt.gc_dump = true; 42 | } 43 | }, 44 | None => {}, 45 | }; 46 | i = i + 1; 47 | } 48 | 49 | opt 50 | } 51 | -------------------------------------------------------------------------------- /rvm/src/deserialize.rs: -------------------------------------------------------------------------------- 1 | //! 2 | //! Deserialization of binary to bytecode 3 | //! 4 | 5 | use bytecode::ByteCode; 6 | use bytecode::ByteCode::*; 7 | 8 | /// Deserialize bytes array form pos_mut 9 | pub fn deserialize(bytes: &[u8], pos_mut: &mut usize) -> ByteCode { 10 | let pos = *pos_mut; 11 | let size = bytes[pos] as usize; 12 | let opcode = bytes[pos + 1]; 13 | let mut operands: Vec = vec![]; 14 | let mut i = 2; 15 | while i < size + 1 { 16 | let mut operand: i32 = 0; 17 | for j in 0..4 { 18 | operand = operand + ((bytes[pos + i + 3 - j] as i32) << (j * 8)); 19 | } 20 | i = i + 4; 21 | operands.push(operand) 22 | } 23 | 24 | let inst = match opcode { 25 | 0 => CALL(operands[0]), 26 | 1 => INVOKE(read_string(bytes[pos + 2 .. pos + size + 1].to_vec())), 27 | 2 => RET, 28 | 3 => JUMP(operands[0]), 29 | 4 => JUMPT(operands[0]), 30 | 5 => JUMPF(operands[0]), 31 | 6 => PUSH(operands[0]), 32 | 7 => POP(operands[0]), 33 | 8 => NEW(operands[0]), 34 | 9 => PUSHA(read_string(bytes[pos + 2 .. pos + size + 1].to_vec())), 35 | 10 => POPA(read_string(bytes[pos + 2 .. pos + size + 1].to_vec())), 36 | 11 => PUSHSTR(read_string(bytes[pos + 2 .. pos + size + 1].to_vec())), 37 | 12 => PUSHINT(operands[0]), 38 | 13 => PUSHBOOL(operands[0]), 39 | 14 => CLASS(operands[0], operands[1], operands[2]), 40 | 15 => SFUNC, 41 | 16 => EBODY(operands[0]), 42 | 17 => PUSHLIST, 43 | 18 => PUSHNIL, 44 | _ => panic!("Not implemented deserialization: {:?}", opcode) 45 | }; 46 | 47 | *pos_mut = pos + size + 1; 48 | 49 | inst 50 | } 51 | 52 | /// Read string embedded in bytes, the string encoding is UTF-8 53 | fn read_string(v: Vec) -> String { 54 | let mut ret: String = "".to_string(); 55 | 56 | if v.len() > 4 { 57 | // Deal with the completely coded segment 58 | match String::from_utf8(v[0..(v.len() - 4)].to_vec()) { 59 | Ok(s) => { 60 | ret = ret + &s; 61 | }, 62 | Err(e) => panic!("error in reading string: {:?}", e) 63 | } 64 | 65 | let mut i = v.len() - 4; 66 | while i < v.len() && v[i] == 0 { 67 | i = i + 1; 68 | } 69 | 70 | if i < v.len() { 71 | match String::from_utf8(v[i..v.len()].to_vec()) { 72 | Ok(s) => { 73 | ret = ret + &s; 74 | }, 75 | Err(e) => panic!("error in reading string: {:?}", e) 76 | } 77 | } 78 | } else { 79 | let mut i = 0; 80 | while i < v.len() && v[i] == 0 { 81 | i = i + 1; 82 | } 83 | 84 | if i < v.len() { 85 | match String::from_utf8(v[i..v.len()].to_vec()) { 86 | Ok(s) => { 87 | ret = ret + &s; 88 | }, 89 | Err(e) => panic!("error in reading string: {:?}", e) 90 | } 91 | } 92 | } 93 | 94 | ret 95 | } -------------------------------------------------------------------------------- /rvm/src/dispatch.rs: -------------------------------------------------------------------------------- 1 | //! 2 | //! Dynamic dispatch 3 | //! 4 | //! We need to explicitly deal with different types of 5 | //! data dynamically. Although Object trait object 6 | //! is also dynamic, but too dangerous to be casted 7 | //! back 8 | //! 9 | 10 | use primitives::*; 11 | use instance::InstanceObj; 12 | use env::Env; 13 | use object::*; 14 | use gc::*; 15 | use self::DynObj::*; 16 | 17 | #[derive(Trace, Clone)] 18 | pub enum DynObj { 19 | Int(IntObj), 20 | Bool(BoolObj), 21 | Str(StrObj), 22 | Ist(InstanceObj), 23 | List(ListObj), 24 | /// None Type, mostly used to initialize empty local vars 25 | Non, 26 | } 27 | 28 | /// Dispatch Object trait calls explicitly at runtime 29 | impl Object for DynObj { 30 | fn invoke(&self, name : &str, stack: &mut Vec>, env: &Env){ 31 | if name == "print" { 32 | print!("{}", &self.to_string()); 33 | return 34 | } 35 | 36 | match self { 37 | &Int(ref intobj) => intobj.invoke(name, stack, env), 38 | &Bool(ref boolobj) => boolobj.invoke(name, stack, env), 39 | &Str(ref strobj) => strobj.invoke(name, stack, env), 40 | &Ist(ref istobj) => istobj.invoke(name, stack, env), 41 | &List(ref listobj) => listobj.invoke(name, stack, env), 42 | &Non => invoke_fail("Nil", name), 43 | } 44 | } 45 | 46 | fn get(&self, name: &str, env: &Env) -> Gc { 47 | match self { 48 | &Int(ref intobj) => intobj.get(name, env), 49 | &Bool(ref boolobj) => boolobj.get(name, env), 50 | &Str(ref strobj) => strobj.get(name, env), 51 | &Ist(ref istobj) => istobj.get(name, env), 52 | &List(ref listobj) => listobj.get(name, env), 53 | &Non => panic!("Non object is not usable"), 54 | } 55 | } 56 | 57 | fn set(&mut self, name: &str, new: &Gc, env: &Env) { 58 | match self { 59 | &mut Int(ref mut intobj) => intobj.set(name, new, env), 60 | &mut Bool(ref mut boolobj) => boolobj.set(name, new, env), 61 | &mut Str(ref mut strobj) => strobj.set(name, new, env), 62 | &mut Ist(ref mut istobj) => istobj.set(name, new, env), 63 | &mut List(ref mut listobj) => listobj.set(name, new, env), 64 | &mut Non => panic!("Non object is not usable"), 65 | } 66 | } 67 | 68 | fn to_string(&self) -> String { 69 | match self { 70 | &Int(ref intobj) => intobj.to_string(), 71 | &Bool(ref listobj) => listobj.to_string(), 72 | &Str(ref strobj) => strobj.to_string(), 73 | &Ist(ref istobj) => istobj.to_string(), 74 | &List(ref listobj) => listobj.to_string(), 75 | &Non => "nil".to_string(), 76 | } 77 | } 78 | 79 | fn tyof(&self) -> String { 80 | match self { 81 | &Int(ref intobj) => intobj.tyof(), 82 | &Bool(ref listobj) => listobj.tyof(), 83 | &Str(ref strobj) => strobj.tyof(), 84 | &Ist(ref istobj) => istobj.tyof(), 85 | &List(ref listobj) => listobj.tyof(), 86 | &Non => "".to_string(), 87 | } 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /rvm/src/env.rs: -------------------------------------------------------------------------------- 1 | //! 2 | //! Runtime Environment 3 | //! 4 | //! The data structure and loader. The 5 | //! environment is assumed to be immutable 6 | //! 7 | 8 | use class::*; 9 | use bytecode::ByteCode; 10 | use deserialize::*; 11 | use std::fs::File; 12 | use std::io::Read; 13 | use function::*; 14 | 15 | pub struct Env { 16 | pub classes : Vec, 17 | pub functions : Vec, 18 | } 19 | 20 | /// Load bytes from a file handler, deserialize, split top-level 21 | /// statements from declarations 22 | pub fn load(f: &mut File) -> (Env, Vec, usize) { 23 | let mut classes = vec![]; 24 | let mut functions = vec![]; 25 | let mut top_code = vec![]; 26 | let mut bytes: Vec = Vec::new(); 27 | let top_n: usize; 28 | 29 | match Read::read_to_end(f, &mut bytes) { 30 | Ok(len) => { 31 | let mut start_pos = 0; 32 | let mut code = vec![]; 33 | loop { 34 | code.push(deserialize(bytes.as_slice(), &mut start_pos)); 35 | if start_pos >= len { 36 | break; 37 | } 38 | } 39 | 40 | let mut pc = 0; 41 | while pc < (code.len() - 1) { 42 | match code[pc] { 43 | ByteCode::CLASS(nattrs, nmtds, father_idx) => { 44 | pc = pc + 1; 45 | let class = parse_class(&code, nattrs as usize, nmtds as usize, 46 | father_idx, &mut pc); 47 | classes.push(class); 48 | }, 49 | ByteCode::SFUNC => { 50 | pc = pc + 1; 51 | functions.push(parse_function(&code, &mut pc)); 52 | }, 53 | ref inst => { 54 | top_code.push(inst.clone()); 55 | pc = pc + 1; 56 | } 57 | } 58 | } 59 | 60 | match code[code.len() - 1] { 61 | ByteCode::PUSHINT(i) => top_n = i as usize, 62 | _ => panic!("The program doesn't end with top-level variables number") 63 | } 64 | }, 65 | Err(e) => { 66 | panic!("reading bytes error: {}", e); 67 | } 68 | } 69 | 70 | (Env { classes : classes, functions : functions }, top_code, top_n) 71 | } 72 | -------------------------------------------------------------------------------- /rvm/src/function.rs: -------------------------------------------------------------------------------- 1 | //! 2 | //! Function structure and parser 3 | //! 4 | //! The size of local variables needed 5 | //! is determined at compile time. 6 | //! 7 | 8 | use bytecode::ByteCode; 9 | 10 | pub struct Function { 11 | pub code: Vec, 12 | pub n_locals: usize, 13 | } 14 | 15 | pub fn parse_function(code: &Vec, pc: &mut usize) -> Function { 16 | let mut cb = vec![]; 17 | 18 | loop { 19 | match code[*pc] { 20 | ByteCode::EBODY(n) => { 21 | *pc = *pc + 1; 22 | return Function { 23 | code: cb, 24 | n_locals: n as usize, 25 | } 26 | }, 27 | _ => { 28 | cb.push(code[*pc].clone()); 29 | *pc = *pc + 1; 30 | } 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /rvm/src/instance.rs: -------------------------------------------------------------------------------- 1 | //! 2 | //! Instance object 3 | //! 4 | 5 | use gc::*; 6 | use object::*; 7 | use class::Class; 8 | use env::Env; 9 | use dispatch::*; 10 | 11 | #[derive(Trace, Clone)] 12 | pub struct InstanceObj { 13 | /// Index of parent class, from which the variable is initialized 14 | pub cls : usize, 15 | /// Attributes values, in the same order as declared 16 | pub attrs : Vec>, 17 | } 18 | 19 | impl InstanceObj { 20 | /// Instance initializer. The attributes are parameter of initialization and put in the 21 | /// same way as common functions 22 | pub fn new(attrs_len: usize, cls_idx: usize, stack: &mut Vec>) -> Gc { 23 | let mut attrs = vec![]; 24 | 25 | for _ in 0..attrs_len { 26 | let obj = stack.pop().unwrap(); 27 | attrs.push(obj.clone()); 28 | } 29 | 30 | Gc::new(DynObj::Ist(InstanceObj { 31 | cls: cls_idx, 32 | attrs: attrs 33 | })) 34 | } 35 | } 36 | 37 | impl Object for InstanceObj { 38 | /// We can't invoke internally because the class's methods will 39 | /// be checked first. 40 | fn invoke(&self, name: &str, _: &mut Vec>, _: &Env) { 41 | invoke_fail("InstanceObj", name) 42 | } 43 | 44 | fn get(&self, name: &str, env: &Env) -> Gc { 45 | let parent: &Class = &env.classes[self.cls as usize]; 46 | 47 | for i in 0..parent.attrs.len() { 48 | if parent.attrs[i] == name.to_string() { 49 | return self.attrs[i].clone(); 50 | } 51 | } 52 | 53 | access_fail("InstanceObj", name) 54 | } 55 | 56 | fn set(&mut self, name: &str, new_obj: &Gc, env: &Env) { 57 | let parent: &Class = &env.classes[self.cls as usize]; 58 | 59 | for i in 0..parent.attrs.len() { 60 | if parent.attrs[i] == name.to_string() { 61 | self.attrs[i] = new_obj.clone(); 62 | } 63 | } 64 | 65 | access_fail("InstanceObj", name); 66 | } 67 | 68 | fn to_string(&self) -> String { 69 | "".to_string() 70 | } 71 | 72 | fn tyof(&self) -> String { 73 | "".to_string() 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /rvm/src/interpret.rs: -------------------------------------------------------------------------------- 1 | //! 2 | //! Intrepreting bytecode instruction 3 | //! 4 | 5 | use env::Env; 6 | use gc::*; 7 | use dispatch::DynObj; 8 | use bytecode::ByteCode; 9 | use bytecode::ByteCode::*; 10 | use object::*; 11 | use function::*; 12 | use class::*; 13 | use instance::InstanceObj; 14 | use super::*; 15 | use primitives::*; 16 | use cmdopt::CmdOpt; 17 | 18 | /// Interpreter State 19 | pub struct Interpreter { 20 | cmd_opt : CmdOpt, 21 | } 22 | 23 | impl Interpreter { 24 | #[inline] 25 | pub fn new(opt: CmdOpt) -> Interpreter { 26 | Interpreter { 27 | cmd_opt : opt, 28 | } 29 | } 30 | 31 | /// Initialize and execute a frame 32 | pub fn run_frame(&self, env: &Env, stack: &mut Vec>, 33 | n_locals: usize, code: &Vec) { 34 | let mut locals : Vec> = init_vec(n_locals, Gc::new(DynObj::Non)); 35 | 36 | let mut pc: usize = 0; 37 | while pc < code.len() { 38 | if self.cmd_opt.debug { 39 | println!("[DEBUG] Executing: {:?}", code[pc]); 40 | } 41 | 42 | // Dealing with frame control instructions 43 | match code[pc] { 44 | // Call global function 45 | CALL(fn_idx) => { 46 | let function: &Function = &env.functions[fn_idx as usize]; 47 | self.run_frame(env, stack, function.n_locals, &function.code); 48 | pc = pc + 1; 49 | }, 50 | // Invoke method of instance (TOS) 51 | INVOKE(ref mtd_name) => { 52 | let recv: Gc = stack.pop().unwrap(); 53 | match *recv { 54 | // Check type at runtime 55 | DynObj::Ist(ref istobj) => { 56 | let class: &Class = &env.classes[istobj.cls as usize]; 57 | // `this` pointer 58 | stack.push(recv.clone()); 59 | match class.get_method(mtd_name, env) { 60 | Some(ref function) => { 61 | self.run_frame(env, stack, function.n_locals, &function.code); 62 | pc = pc + 1; 63 | }, 64 | None => { 65 | panic!("FATAL: invoke built-in methods on instance") 66 | } 67 | } 68 | }, 69 | _ => { 70 | recv.invoke(mtd_name, stack, env); 71 | }, 72 | }; 73 | pc = pc + 1 74 | }, 75 | RET => { 76 | return; 77 | }, 78 | ref other => { 79 | self.interpret(env, other, stack, &mut locals, &mut pc); 80 | pc = pc + 1; 81 | } 82 | } 83 | } 84 | } 85 | 86 | /// Interpret in-frame instructions 87 | fn interpret(&self, env: &Env, inst: &ByteCode, stack: &mut Vec>, 88 | locals: &mut Vec>, pc: &mut usize) { 89 | match inst { 90 | &JUMP(offset) => { 91 | // offset might be negative 92 | *pc = (*pc as Integer + offset) as usize; 93 | }, 94 | &JUMPT(offset) => { 95 | jump_if(stack, pc, true, offset); 96 | }, 97 | &JUMPF(offset) => { 98 | jump_if(stack, pc, false, offset); 99 | }, 100 | &PUSH(idx) => { 101 | stack.push(locals[idx as usize].clone()); 102 | }, 103 | &POP(idx) => { 104 | let obj = stack.pop().unwrap(); 105 | locals[idx as usize] = obj.clone(); 106 | }, 107 | &NEW(cls_idx) => { 108 | let class: &Class = &env.classes[cls_idx as usize]; 109 | let obj = InstanceObj::new(class.attrs.len(), cls_idx as usize, stack); 110 | stack.push(obj); 111 | }, 112 | &PUSHA(ref attr_name) => { 113 | let tos = stack.pop().unwrap(); 114 | stack.push(tos.get(attr_name, env)); 115 | }, 116 | &POPA(ref attr_name) => { 117 | let tos = stack.pop().unwrap(); 118 | let mut ntos_copy: DynObj = (*stack.pop().unwrap()).clone(); 119 | ntos_copy.set(attr_name, &tos, env); 120 | stack.push(Gc::new(ntos_copy)); 121 | 122 | /* 123 | Although in fact we copied the object entirely, but only 124 | pointers are copied so the time overhead is not significant. 125 | 126 | Beyond that, the original NTOS is popped out and will be 127 | garbage-collected sometime, so the references to the unmodified 128 | parts are actually intact. 129 | */ 130 | }, 131 | &PUSHSTR(ref s) => { 132 | stack.push(StrObj::new(s.clone())); 133 | }, 134 | &PUSHINT(i) => { 135 | stack.push(IntObj::new(i)); 136 | }, 137 | &PUSHBOOL(i) => { 138 | if i == 0 { 139 | stack.push(BoolObj::new(false)); 140 | } else { 141 | stack.push(BoolObj::new(true)); 142 | } 143 | }, 144 | &PUSHLIST => { 145 | stack.push(Gc::new(DynObj::List(ListObj::Empty))); 146 | }, 147 | &PUSHNIL => { 148 | stack.push(Gc::new(DynObj::Non)); 149 | } 150 | other => { panic!("{:?}'s interpretation is not implemented", other) } 151 | } 152 | } 153 | } 154 | 155 | /// Jump if TOS is bool object and its value is equal to `cond` 156 | #[inline] 157 | fn jump_if(stack: &mut Vec>, pc: &mut usize, cond: bool, offset: Integer) { 158 | let tos = stack.pop().unwrap(); 159 | match *tos { 160 | DynObj::Bool(ref boolobj) => { 161 | if boolobj.val == cond { 162 | // offset might be negative 163 | *pc = (*pc as Integer + offset) as usize; 164 | } 165 | } 166 | _ => panic!("JUMPT error: TOS is not bool") 167 | } 168 | } 169 | 170 | /// Initialize a vector of length `len` and full of `init` 171 | #[inline] 172 | fn init_vec(len: usize, init: T) -> Vec { 173 | let mut v = Vec::with_capacity(len); 174 | for _ in 0..len { 175 | v.push(init.clone()); 176 | } 177 | v 178 | } 179 | -------------------------------------------------------------------------------- /rvm/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! 2 | //! The RuScript Virtual Machine (RVM) 3 | //! 4 | //! ## Loading stage 5 | //! loader read in bytes, deserialize them 6 | //! into bytecodes, structure them into declarations 7 | //! and top-level bytecodes. 8 | //! 9 | //! ## Execution stage 10 | //! The executor will start from top-level bytecodes, 11 | //! transfer control from and to new frames initialized 12 | //! from global functions or object methods 13 | //! 14 | //! ## Code structure 15 | //! * Data structure 16 | //! + Static: `class`, `function` 17 | //! + Dynamic: `primitives`, `instance` 18 | //! * Bytecode 19 | //! + Format: `bytecode` 20 | //! + Deserialization: `deserialize` 21 | //! * Object 22 | //! + Unified interface: `object` 23 | //! + Dynamic dispatch: `dispatch` 24 | //! * Runtime 25 | //! + Loading: `env` 26 | //! + Execution: `interpret` 27 | //! 28 | 29 | #![crate_name = "rvm"] 30 | 31 | #![feature(proc_macro, custom_derive, test)] 32 | 33 | #[macro_use] 34 | extern crate gc_derive; 35 | extern crate gc; 36 | extern crate test; 37 | 38 | /// 32-bit Integer is the standard of bytecode and Int type 39 | pub type Integer = i32; 40 | 41 | pub mod bytecode; 42 | pub mod deserialize; 43 | pub mod class; 44 | pub mod env; 45 | pub mod object; 46 | pub mod instance; 47 | pub mod primitives; 48 | pub mod dispatch; 49 | pub mod interpret; 50 | pub mod function; 51 | pub mod cmdopt; 52 | -------------------------------------------------------------------------------- /rvm/src/main.rs: -------------------------------------------------------------------------------- 1 | extern crate rvm; 2 | extern crate gc; 3 | 4 | use std::fs::File; 5 | use std::env; 6 | use rvm::cmdopt::*; 7 | use rvm::interpret::Interpreter; 8 | 9 | fn main() { 10 | if let Some(ref src) = env::args().nth(1) { 11 | 12 | let opt = parse_opt(); 13 | 14 | let mut f_opt = File::open(src); 15 | match f_opt { 16 | Ok(ref mut f) => { 17 | let (e, top, top_n) = rvm::env::load(f); 18 | let interpreter = Interpreter::new(opt.clone()); 19 | 20 | let times: usize = match opt.bench { 21 | None => 1, 22 | Some(ref n) => n.clone(), 23 | }; 24 | 25 | for _ in 0..times { 26 | let mut stack = vec![]; 27 | interpreter.run_frame(&e, &mut stack, top_n, &top); 28 | } 29 | }, 30 | Err(err) => panic!("Read binary error: {:?}", err), 31 | } 32 | } else { 33 | println!("Usage: {} ", env::args().nth(0).unwrap()); 34 | } 35 | 36 | gc::force_collect(); 37 | } 38 | -------------------------------------------------------------------------------- /rvm/src/object.rs: -------------------------------------------------------------------------------- 1 | //! 2 | //! Object trait interface 3 | //! 4 | //! Every thing allocated in heap, traced by GC, 5 | //! and referenced through local variable and global 6 | //! stack should implement this trait interface 7 | //! 8 | //! 9 | 10 | use gc::*; 11 | use std::fmt::Debug; 12 | use env::Env; 13 | use dispatch::*; 14 | 15 | pub trait Object : Trace { 16 | #[allow(unused_variables)] 17 | fn invoke(&self, name: &str, stack: &mut Vec>, env: &Env); 18 | 19 | #[allow(unused_variables)] 20 | fn get(&self, name: &str, env: &Env) -> Gc { 21 | access_fail(&self.tyof(), name) 22 | } 23 | 24 | #[allow(unused_variables)] 25 | fn set(&mut self, name: &str, new: &Gc, env: &Env) { 26 | access_fail(&self.tyof(), name); 27 | } 28 | 29 | fn to_string(&self) -> String; 30 | 31 | fn tyof(&self) -> String; 32 | } 33 | 34 | pub fn invoke_fail(ty: &str, name: &str) { 35 | panic!("{:?} has no such method {:?}", ty, name) 36 | } 37 | 38 | pub fn access_fail(ty: &str, name: N) -> Gc where N: Debug { 39 | panic!("{:?} has no such attr {:?}", ty, name) 40 | } 41 | -------------------------------------------------------------------------------- /rvm/src/primitives.rs: -------------------------------------------------------------------------------- 1 | //! 2 | //! Primitive types object 3 | //! 4 | 5 | use super::*; 6 | use gc::*; 7 | use object::*; 8 | use env::Env; 9 | use dispatch::DynObj; 10 | use self::ListObj::*; 11 | 12 | #[derive(Trace, Clone)] 13 | pub struct IntObj { 14 | pub val: Integer, 15 | } 16 | 17 | #[derive(Trace, Clone)] 18 | pub struct BoolObj { 19 | pub val: bool, 20 | } 21 | 22 | #[derive(Trace, Clone)] 23 | pub struct StrObj { 24 | pub val: String 25 | } 26 | 27 | #[derive(Trace, Clone)] 28 | pub enum ListObj { 29 | Empty, 30 | Cons { 31 | hd: Gc, 32 | tl: Gc, 33 | }, 34 | } 35 | 36 | 37 | impl IntObj { 38 | pub fn new(i: Integer) -> Gc { 39 | Gc::new(DynObj::Int(IntObj { val : i })) 40 | } 41 | } 42 | 43 | impl BoolObj { 44 | pub fn new(b: bool) -> Gc { 45 | Gc::new(DynObj::Bool(BoolObj { val : b })) 46 | } 47 | } 48 | 49 | 50 | impl StrObj { 51 | pub fn new(s: String) -> Gc { 52 | Gc::new(DynObj::Str(StrObj { val : s })) 53 | } 54 | } 55 | 56 | impl ListObj { 57 | pub fn new(head: Gc, tail: Gc) -> Gc { 58 | Gc::new(DynObj::List(Cons { 59 | hd : head, 60 | tl : tail, 61 | })) 62 | } 63 | 64 | #[allow(unused_variables)] 65 | pub fn len(&self) -> Integer { 66 | match self { 67 | &Empty => 0, 68 | &Cons{ ref hd, ref tl } => { 69 | match **tl { 70 | DynObj::List(ref l) => 1 + l.len(), 71 | _ => panic!("illegal type in list"), 72 | } 73 | } 74 | } 75 | } 76 | 77 | pub fn at(&self, i: Integer) -> Gc { 78 | match self { 79 | &Empty => panic!("Can't access element of an empty list"), 80 | &Cons{ ref hd, ref tl } => { 81 | if i == 0 { 82 | hd.clone() 83 | } else { 84 | match **tl { 85 | DynObj::List(ref l) => l.at(i), 86 | _ => panic!("illegal type in list"), 87 | } 88 | } 89 | } 90 | } 91 | } 92 | } 93 | 94 | impl Object for IntObj { 95 | /// Built-in functions: `add`, `print` 96 | fn invoke(&self, name : &str, stack: &mut Vec>, _ : &Env) { 97 | match name { 98 | "add" => { 99 | let b = stack.pop().unwrap(); 100 | match *b { 101 | DynObj::Int(ref intobj) => { 102 | stack.push(IntObj::new(self.val + intobj.val)); 103 | }, 104 | ref other => { 105 | panic!("invalid type for add: {:?}", other.tyof()); 106 | } 107 | } 108 | }, 109 | "le" => { 110 | let b = stack.pop().unwrap(); 111 | match *b { 112 | DynObj::Int(ref intobj) => { 113 | stack.push(BoolObj::new(self.val <= intobj.val)); 114 | }, 115 | ref other => { 116 | panic!("invalid type for le: {:?}", other.tyof()); 117 | } 118 | } 119 | } 120 | other => invoke_fail("IntObj", other) 121 | } 122 | } 123 | 124 | fn to_string(&self) -> String { self.val.to_string() } 125 | 126 | 127 | fn tyof(&self) -> String { format!("({})", self.val) } 128 | } 129 | 130 | 131 | impl Object for BoolObj { 132 | /// Built-in functions: `not`, `print` 133 | fn invoke(&self, name : &str, stack: &mut Vec>, _ : &Env){ 134 | match name { 135 | "not" => { 136 | stack.push(BoolObj::new(!self.val)); 137 | }, 138 | other => invoke_fail("BoolObj", other) 139 | } 140 | } 141 | 142 | fn to_string(&self) -> String { 143 | self.val.to_string() 144 | } 145 | 146 | fn tyof(&self) -> String { format!("({})", self.val) } 147 | } 148 | 149 | impl Object for StrObj { 150 | /// Built-in functions: `print` 151 | fn invoke(&self, name : &str, _: &mut Vec>, _ : &Env) { 152 | match name { 153 | other => invoke_fail("StrObj", other) 154 | } 155 | } 156 | 157 | fn to_string(&self) -> String { 158 | self.val.to_string() 159 | } 160 | 161 | fn tyof(&self) -> String { format!("({})", self.val) } 162 | } 163 | 164 | 165 | impl Object for ListObj { 166 | /// Built-in functions: `print` 167 | fn invoke(&self, name : &str, stack: &mut Vec>, _ : &Env) { 168 | match name { 169 | "cons" => { 170 | let b = stack.pop().unwrap(); 171 | stack.push(ListObj::new(b, Gc::new(DynObj::List(self.clone())))); 172 | }, 173 | "len" => { 174 | stack.push(IntObj::new(self.len())); 175 | }, 176 | "at" => { 177 | let b = stack.pop().unwrap(); 178 | match *b { 179 | DynObj::Int(ref intobj) => { 180 | stack.push(self.at(intobj.val)); 181 | }, 182 | ref other => { 183 | panic!("invalid type for at: {:?}", other.tyof()); 184 | } 185 | } 186 | }, 187 | other => invoke_fail("ListObj", other) 188 | } 189 | } 190 | 191 | fn to_string(&self) -> String { 192 | match self { 193 | &Empty => "[]".to_string(), 194 | &Cons{ ref hd, ref tl } => { 195 | let mut s = String::from("["); 196 | s.push_str(&hd.to_string()); 197 | 198 | match **tl { 199 | DynObj::List(ref l) => { 200 | match *l { 201 | Empty => { 202 | s.push(']'); 203 | s 204 | }, 205 | _ => { 206 | s.push_str(", "); 207 | s.push_str(&tl.to_string()); 208 | s 209 | } 210 | } 211 | }, 212 | _ => panic!("illegal type in list"), 213 | } 214 | } 215 | } 216 | } 217 | 218 | fn tyof(&self) -> String { "".to_string() } 219 | } 220 | -------------------------------------------------------------------------------- /spec/Bytecode.md: -------------------------------------------------------------------------------- 1 | Bytecode Instructions 2 | ---- 3 | 4 | ## Instructions 5 | 6 | Please refer to `src/bytecode.rs` for the newest version. 7 | 8 | ## Call Convention 9 | For function `f(a, b, c)`, it will assume that the parameters are put on stack, TOS is `a`, NTOS is `b`, ...; For method, it will assume the TOS is `this` pointer, although it doesn't appear explicitly in the parameter list. When function/method returns, if it returns some value, this object should reside on the top of stack, or nothing special happens if it doesn't return any value. 10 | 11 | ## Other layout issues 12 | 13 | ### Class declaration 14 | Example: 15 | 16 | ```haskell 17 | class A : inherits X { 18 | a: Int 19 | b: Bool 20 | 21 | fn c(...) { 22 | ... 23 | } 24 | virtual fn d(...) { 25 | ... 26 | } 27 | } 28 | ``` 29 | 30 | will be compiled to such layout: 31 | 32 | ```python 33 | CLASS(2, 1, 0) # n_attrs, n_methods, father_idx 34 | PUSHSTR("a") 35 | PUSHSTR("b") 36 | ### bytecode of function body 37 | EBODY(2) # n_locals 38 | PUSHSTR("c") 39 | ### End. The virtual function will not be compiled at all 40 | ``` 41 | 42 | ### Function declaration 43 | Start with the `SFUNC` and ends with the `EBODY(n_locals)`. 44 | 45 | -------------------------------------------------------------------------------- /spec/Compiler.md: -------------------------------------------------------------------------------- 1 | # Compiler Design 2 | 3 | The compiler needs to compile the RuScript language into RuScript bytecode. First, it should enforce the correctness of advanced semantics; Second, it should try to eliminate the majority of runtime error through static checking; Third, it should try to optimize the bytecode to archive better efficiency and smaller binary size. 4 | 5 | ## High-level Language Design 6 | The [model](./Model.md) has already given some suggestion over how the language looks like. Here is a more formal description. 7 | 8 | ``` 9 | program := [ stmt | decl ] 10 | 11 | decl := fnDecl 12 | | clsDecl 13 | 14 | stmt := 'var' binding <'=' expr> 15 | | x '=' expr 16 | | 'if' expr { [stmt] } <'else' { [stmt] }> 17 | | 'while' expr { [stmt] } 18 | | 'return' expr 19 | | 'break' 20 | 21 | lhs := x 22 | | x.attr 23 | 24 | expr := x 25 | | x.attr 26 | | x.f([ expr ]) 27 | | cls([ expr ]) 28 | | literal 29 | 30 | literal := string 31 | | integer 32 | | boolean 33 | 34 | fnDecl := fnSig { [stmt] } 35 | 36 | binding := x : type 37 | 38 | type := 'bool' 39 | | 'int' 40 | | 'str' 41 | | cls 42 | 43 | clsDecl := 'class' cls <'inherits' cls> { [attr] [method] } 44 | 45 | attr := <'private'> binding 46 | 47 | method := <'private'> fnDecl 48 | | <'private'> 'virtual' fnSig 49 | 50 | fnSig := 'fn' f ([ binding ]) 51 | 52 | x := 'self' 53 | | [alpha | digit] 54 | 55 | ``` 56 | 57 | Note: `<...>` means optional, `...|...` means selection, `[...]` means many, `'...'` means reserved name. However, this description is not very strict. 58 | 59 | ## Features 60 | * Control-flow statements 61 | * Object-Oriented 62 | + Inheritance 63 | + Polymorphism through virtual method 64 | + Encapsulation through `private` keyword 65 | * Static checking 66 | + Built-in types 67 | + Contravariant/Covariant 68 | 69 | The effect of encapsulation is check statically. The poly and inheritance will be checked, but implemented on a runtime basis. 70 | 71 | ## Change Log 72 | Compared with the previous version, the new version changed in the following ways: 73 | 74 | * **Simplified the variable scoping and runtime state**: The top-level and function/methods can only access its own locally declared variables. 75 | * **Writable attribute** 76 | * **Introduction of function declaration** 77 | * **Introduction of type annotation** 78 | * **Introduction of control-flow statements** 79 | * **Introduction of OO advanced features** 80 | 81 | ## Implementation 82 | It should be modular. The code-generation can be expressed as a separate problem of transforming a graph into a linear ordering. The name to index environment should be a separate module. The structural mapping can also be abstracted out. The optimization pass should be written more generic. 83 | 84 | 85 | 86 | 87 | 88 | -------------------------------------------------------------------------------- /spec/Model.md: -------------------------------------------------------------------------------- 1 | # Runtime Model 2 | 3 | 4 | ## Description 5 | 6 | The runtime is basically composed of 7 | 8 | * Loader 9 | * Interpreter 10 | * Garbage collector (The actual implementation is in `rust-gc` library) 11 | 12 | ### Load 13 | 14 | The loader loads in the binary, parse bytecode from it, and structure the program text into top-level statements and declarations (of functions and classes). 15 | 16 | ### Interpret 17 | 18 | The interpreter executes frames. The global environment is initialized from the declarations, plus a global R/W stack. The root frame is initialized from the top-level statements. Every function-call or method-invocation will create new frame from function body. 19 | 20 | ### Frame 21 | 22 | Every frame contains its local variables and access them by integer-indexing. The arguments are passed by stack. The return value is returned by stack. 23 | 24 | ### Value 25 | 26 | Every value (including primitives and instance) is boxed and garbage-collected. Thus, the values in local variable array and global data stack are pointers, and they are all the references that need to be traced by collector. 27 | 28 | ### Primitive types 29 | 30 | * Boolean 31 | * 32-bit Integer 32 | * String 33 | 34 | ### Imperative control-flow statements 35 | 36 | * Conditional Branching 37 | * Conditional Looping 38 | 39 | ### Built-in 40 | 41 | * I/O 42 | * Heap allocation 43 | * Reference to `self` or `this` 44 | 45 | ## Epilogue 46 | This is still an experimental project. When OO is fixed, the concurrency is the next topic to explore. And also module system and FFI implementation. But here are two points worth attention: 47 | 48 | * Reduce introduction of new bytecode instructions 49 | * Designs should be as orthogonal as possible
 50 | 51 | -------------------------------------------------------------------------------- /spec/machine/machine.md: -------------------------------------------------------------------------------- 1 | # Abstract Machine 2 | 3 | ## Convention 4 | We use $\#$ to destruct an abstract symbol into product tuple as defined. 5 | 6 | In abstract definition, $[p]$ means a vector of $p$; $[k \mapsto v]$ means a partial mapping from $k$ to $v$. Mapping is always represented by $m$ 7 | 8 | In concrete rules, $[p]$ means a vector of $p$, $m[k \mapsto v]$ means update mapping $m$ with $v$ indexed by $k$, $[p]@i$ means the $i$th element of $[p]$ vector. $m@k$ means the element of $m$ indexed by $k$ 9 | 10 | $p?$ means an `Option` type in abstract definition, we use $p$ to match the `Some` case, and $\texttt{None}$ to match a `None` case. 11 | 12 | NOTE: This model is only to provide an intuition for formalization. It might not reflect the implementation as it is. 13 | 14 | ## Abstract Object 15 | Class: $Cls \stackrel{def}{\equiv} (i?, [s \mapsto f], [s])$ 16 | 17 | Function: $f \stackrel{def}{\equiv} ([I], i)$ 18 | 19 | Instance Object: $ist \stackrel{def}{\equiv} (i, [x])$ 20 | 21 | Any object: $x, top, next ...$ 22 | 23 | Primitives: 24 | 25 | * string: $s$ 26 | * boolean: $b = \{ \textbf{tt},\textbf{ff}\}$ 27 | * integer: $i$ 28 | * nil: $\{\textbf{nil}\}$ 29 | 30 | Other symbols: 31 | 32 | * Local variables: $l \stackrel{def}{\equiv} [x]$ 33 | * Environment: $E \stackrel{def}{\equiv} ([Cls], [f])$ 34 | * Program Counter: $c \stackrel{def}{\equiv} i$ 35 | * Stack: $S$, supporting list pattern to express pop and push, for example, $x:y:S$ means $S$ is at least 2 elements long, and top element is $x$, next element is $y$ 36 | * Implicit Side-effects: $\epsilon$ 37 | 38 | ## Local state transition 39 | 40 | Transition: $$E \vDash \langle I, (l, c,S) \rangle \triangleright (l', c', S')$$ 41 | 42 | Rewriting rules: 43 | 44 | 45 | $$\begin{aligned} 46 | E &\vDash \langle \texttt{JUMP}(i), (l, c,S) \rangle &\triangleright & \quad (l, c + i, S) \\ 47 | E &\vDash \langle \texttt{JUMPT}(i), (l, c, \mathbf{tt}:S) \rangle &\triangleright & \quad (l, c + i, S) \\ 48 | E &\vDash \langle \texttt{JUMPT}(i), (l, c, \mathbf{ff}:S) \rangle &\triangleright & \quad (l, c, S) \\ 49 | E &\vDash \langle \texttt{JUMPF}(i), (l, c, \mathbf{ff}:S) \rangle &\triangleright & \quad (l, c + i, S) \\ 50 | E &\vDash \langle \texttt{JUMPF}(i), (l, c, \mathbf{tt}:S) \rangle &\triangleright & \quad (l, c, S) \\ 51 | E &\vDash \langle \texttt{PUSH}(i), (l, c,S) \rangle &\triangleright & \quad (l, c, l[i]:S) \quad \text{if len(l) > i}\\ 52 | E &\vDash \langle \texttt{POP}(i), (l, c, top:S) \rangle &\triangleright & \quad (l[i \mapsto top], c, S) \quad \text{if len(l) > i}\\ 53 | E &\vDash \langle \texttt{NEW}(i), (l, c,S) \rangle &\triangleright & \quad (l, c, \texttt{new}(E, i, S):S) \\ 54 | E &\vDash \langle \texttt{PUSHA}(s), (l, c, top:S) \rangle &\triangleright & \quad (l, c, \texttt{get}(top,s):S) \\ 55 | E &\vDash \langle \texttt{POPA}(s), (l, c, top:next:S) \rangle &\triangleright & \quad (l, c, \texttt{set}(\texttt{copy}(next), s, top, E):S) \\ 56 | E &\vDash \langle \texttt{PUSHSTR}(s), (l, c,S) \rangle &\triangleright & \quad (l, c, s:S) \\ 57 | E &\vDash \langle \texttt{PUSHINT}(i), (l, c,S) \rangle &\triangleright & \quad (l, c, i:S) \\ 58 | E &\vDash \langle \texttt{PUSHBOOL}(i), (l, c,S) \rangle &\triangleright & \quad (l, c, \mathbf{ff}:S) \quad \text{if i = 0}\\ 59 | E &\vDash \langle \texttt{PUSHBOOL}(i), (l, c,S) \rangle &\triangleright & \quad (l, c, \mathbf{tt}:S) \quad \text{if i <> 0}\\ 60 | E &\vDash \langle \texttt{PUSHLIST}, (l, c,S) \rangle &\triangleright & \quad (l, c, []:S) \\ 61 | E &\vDash \langle \texttt{PUSHNIL}, (l, c,S) \rangle &\triangleright & \quad (l, c, \mathbf{nil}:S) \\ 62 | \end{aligned}$$ 63 | 64 | ## Frame state transition (single-step) 65 | 66 | Transition: $$E \vDash \langle I, (l, c,S) \rangle \rightarrow (l', c', S'), \epsilon$$ 67 | 68 | Rewriting Rules: 69 | 70 | 71 | $$ 72 | \frac{\texttt{run}(E, [f]@i, S) \leadsto S', \epsilon} 73 | {E\#([Cls], [f]) \vDash \langle \texttt{CALL}(i), (l, c,S) \rangle \rightarrow (l, c + 1, S'), \epsilon } 74 | $$$$ 75 | \frac{\texttt{run}(E, \texttt{method}(E, s, ist), ist:S) \leadsto S', \epsilon} 76 | {E \vDash \langle \texttt{INVOKE(s)}, (l, c, ist:S) \rangle \rightarrow (l, c + 1, S'), \epsilon}\\ 77 | $$$$ 78 | \frac{\texttt{run}(E, \texttt{method}(E, s, ist), ist:S) \leadsto S', \epsilon} 79 | {E \vDash \langle \texttt{INVOKE(s)}, (l, c, obj:S) \rangle \rightarrow (l, c + 1, S), \epsilon}\\ 80 | $$$$ 81 | \frac{E \vDash \langle I, (l, c, S) \rangle \triangleright (l', c', S')} 82 | {E \vDash \langle I, (l, c, S) \rangle \rightarrow (l', c' + 1, S'), \emptyset} 83 | $$ 84 | 85 | 86 | ## Frame state transition (multi-step) 87 | 88 | Transition: $$E, C \vDash (l, c, S) \rightarrow^* S', \epsilon$$ 89 | 90 | Rules: 91 | $$\vDash \langle \texttt{RET}, (l, c, S) \rangle \rightarrow^* S, \emptyset $$ 92 | 93 | $$\frac{E, C \vDash \langle C[c], (l, c,S) \rangle \rightarrow (l', c', S'), \epsilon \quad 94 | E, C \vDash (l', c', S') \rightarrow^* S'', \epsilon'} 95 | {E, C \vDash (l, c, S) \rightarrow^* S'', \epsilon \cup \epsilon'}$$ 96 | 97 | 98 | ## Misc 99 | 100 | $$\texttt{run}(E, (C, i), S) \leadsto S', \epsilon = 101 | E, C \vDash (l, 0, S) \rightarrow^* S', \epsilon \quad \text{where } l = i * [0]$$ 102 | 103 | $$\texttt{new}(([Cls], [f]), i, S) = \texttt{InstanceObj}(k, i, S) \quad k = \texttt{len}(\texttt{attrs}([Cls]@i)$$ 104 | 105 | $$\texttt{method}(([Cls], \_), s, ist\#(i, \_)) = m@s \quad \text{where } ([Cls]@i)\#(\_, m, \_)$$ 106 | -------------------------------------------------------------------------------- /tests/add.rus: -------------------------------------------------------------------------------- 1 | var a : Int = 1; 2 | var b : Int = 2; 3 | var c : Int = a.add(b); 4 | 5 | c.print(); 6 | -------------------------------------------------------------------------------- /tests/conc.rus: -------------------------------------------------------------------------------- 1 | # Concurrency (Not implemented yet) 2 | 3 | import conc 4 | 5 | fn printOne() { 6 | var a : Int = 1; 7 | while (True) { 8 | a.print(); 9 | } 10 | } 11 | 12 | fn printTwo() { 13 | var a : Int = 2; 14 | while (True) { 15 | a.print(); 16 | } 17 | } 18 | 19 | printOne(); 20 | fork(printTwo).join(); # from conc 21 | -------------------------------------------------------------------------------- /tests/control.rus: -------------------------------------------------------------------------------- 1 | var b : Bool = False; 2 | 3 | if (b) { 4 | var c : Int = 1; 5 | c.print(); 6 | } else { 7 | var c : Int = 2; 8 | c.print(); 9 | } 10 | -------------------------------------------------------------------------------- /tests/inheritance.rus: -------------------------------------------------------------------------------- 1 | # Demo of RuScript 2 | 3 | class Y { 4 | virtual fn id(y: Int) -> Int 5 | } 6 | 7 | class X inherits Y { 8 | fn id(y: Int) -> Int { 9 | return y; 10 | } 11 | } 12 | 13 | fn callId(obj: Y) { 14 | var a : Int = 1; 15 | a = obj.id(a); 16 | a.print(); 17 | } 18 | 19 | var obj : X = new X(); 20 | 21 | callId(obj); 22 | -------------------------------------------------------------------------------- /tests/invoke.rus: -------------------------------------------------------------------------------- 1 | var x : Int = ((1).add(2)).add(3); 2 | 3 | x.print(); 4 | -------------------------------------------------------------------------------- /tests/list.rus: -------------------------------------------------------------------------------- 1 | var x : List = []; 2 | 3 | var y : Int = 1; 4 | 5 | var z : List = x.cons(y); 6 | 7 | z.print(); 8 | -------------------------------------------------------------------------------- /tests/mod.rus: -------------------------------------------------------------------------------- 1 | import inheritance 2 | 3 | var obj : inheritance::X = new inheritance::X(); 4 | 5 | inheritance::callId(obj); 6 | -------------------------------------------------------------------------------- /tests/nil.rus: -------------------------------------------------------------------------------- 1 | var n : Nil = nil; 2 | 3 | n.print(); 4 | -------------------------------------------------------------------------------- /tests/vis.rus: -------------------------------------------------------------------------------- 1 | class X { 2 | private pri : Int 3 | } 4 | 5 | var x : X = new X(1); 6 | 7 | x.pri = 2; 8 | --------------------------------------------------------------------------------