├── .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 |
--------------------------------------------------------------------------------