Implement the while loop
This commit is contained in:
parent
0c9799b887
commit
f7b36cb81d
@ -1 +1 @@
|
|||||||
3.3.5
|
3.3.6
|
||||||
|
2
TODO
2
TODO
@ -14,8 +14,6 @@
|
|||||||
- Each temporary variable gets a tn register where n is the variable index. If
|
- Each temporary variable gets a tn register where n is the variable index. If
|
||||||
there more variables the allocation will fail with out of bounds runtime
|
there more variables the allocation will fail with out of bounds runtime
|
||||||
error. Implement spill over.
|
error. Implement spill over.
|
||||||
- The allocator puts temporary and local variables into the same registers,
|
|
||||||
causing conflicts.
|
|
||||||
|
|
||||||
# Language
|
# Language
|
||||||
|
|
||||||
|
@ -5,6 +5,7 @@ module Language.Elna.Backend.Allocator
|
|||||||
) where
|
) where
|
||||||
|
|
||||||
import Data.HashMap.Strict (HashMap)
|
import Data.HashMap.Strict (HashMap)
|
||||||
|
import Data.Int (Int32)
|
||||||
import Data.Word (Word32)
|
import Data.Word (Word32)
|
||||||
import Data.Vector (Vector)
|
import Data.Vector (Vector)
|
||||||
import Language.Elna.Backend.Intermediate
|
import Language.Elna.Backend.Intermediate
|
||||||
@ -17,11 +18,20 @@ import Language.Elna.Location (Identifier(..))
|
|||||||
import Control.Monad.Trans.Reader (ReaderT, asks, runReaderT)
|
import Control.Monad.Trans.Reader (ReaderT, asks, runReaderT)
|
||||||
import Control.Monad.Trans.State (State, runState, modify')
|
import Control.Monad.Trans.State (State, runState, modify')
|
||||||
import GHC.Records (HasField(..))
|
import GHC.Records (HasField(..))
|
||||||
import Control.Monad.Trans.Class (MonadTrans(lift))
|
import Control.Monad.Trans.Class (MonadTrans(..))
|
||||||
|
import Control.Monad.Trans.Except (ExceptT(..), runExceptT, throwE)
|
||||||
|
import Data.List ((!?))
|
||||||
|
|
||||||
data Store r
|
data Store r
|
||||||
= RegisterStore r
|
= RegisterStore r
|
||||||
| StackStore Word32 r
|
| StackStore Int32 r
|
||||||
|
|
||||||
|
data AllocationError = AllocationError
|
||||||
|
deriving Eq
|
||||||
|
|
||||||
|
instance Show AllocationError
|
||||||
|
where
|
||||||
|
show = const "Ran out of registers during register allocation"
|
||||||
|
|
||||||
newtype MachineConfiguration r = MachineConfiguration
|
newtype MachineConfiguration r = MachineConfiguration
|
||||||
{ temporaryRegisters :: [r]
|
{ temporaryRegisters :: [r]
|
||||||
@ -32,7 +42,7 @@ newtype MachineState = MachineState
|
|||||||
} deriving (Eq, Show)
|
} deriving (Eq, Show)
|
||||||
|
|
||||||
newtype Allocator r a = Allocator
|
newtype Allocator r a = Allocator
|
||||||
{ runAllocator :: ReaderT (MachineConfiguration r) (State MachineState) a
|
{ runAllocator :: ExceptT AllocationError (ReaderT (MachineConfiguration r) (State MachineState)) a
|
||||||
}
|
}
|
||||||
|
|
||||||
instance forall r. Functor (Allocator r)
|
instance forall r. Functor (Allocator r)
|
||||||
@ -52,20 +62,22 @@ allocate
|
|||||||
:: forall r
|
:: forall r
|
||||||
. MachineConfiguration r
|
. MachineConfiguration r
|
||||||
-> HashMap Identifier (Vector (Quadruple Variable))
|
-> HashMap Identifier (Vector (Quadruple Variable))
|
||||||
-> HashMap Identifier (ProcedureQuadruples (Store r))
|
-> Either AllocationError (HashMap Identifier (ProcedureQuadruples (Store r)))
|
||||||
allocate machineConfiguration = fmap function
|
allocate machineConfiguration = traverse function
|
||||||
where
|
where
|
||||||
function :: Vector (Quadruple Variable) -> ProcedureQuadruples (Store r)
|
run = flip runState (MachineState{ stackSize = 0 })
|
||||||
|
. flip runReaderT machineConfiguration
|
||||||
|
. runExceptT
|
||||||
|
. runAllocator
|
||||||
|
. mapM quadruple
|
||||||
|
function :: Vector (Quadruple Variable) -> Either AllocationError (ProcedureQuadruples (Store r))
|
||||||
function quadruples' =
|
function quadruples' =
|
||||||
let (result, lastState)
|
let (result, lastState) = run quadruples'
|
||||||
= flip runState (MachineState{ stackSize = 0 })
|
in makeResult lastState <$> result
|
||||||
$ flip runReaderT machineConfiguration
|
makeResult MachineState{ stackSize } result = ProcedureQuadruples
|
||||||
$ runAllocator
|
{ quadruples = result
|
||||||
$ mapM quadruple quadruples'
|
, stackSize = stackSize
|
||||||
in ProcedureQuadruples
|
}
|
||||||
{ quadruples = result
|
|
||||||
, stackSize = getField @"stackSize" lastState
|
|
||||||
}
|
|
||||||
|
|
||||||
quadruple :: Quadruple Variable -> Allocator r (Quadruple (Store r))
|
quadruple :: Quadruple Variable -> Allocator r (Quadruple (Store r))
|
||||||
quadruple = \case
|
quadruple = \case
|
||||||
@ -131,11 +143,15 @@ operand (VariableOperand variableOperand) =
|
|||||||
|
|
||||||
storeVariable :: Variable -> Allocator r (Store r)
|
storeVariable :: Variable -> Allocator r (Store r)
|
||||||
storeVariable (TempVariable index) = do
|
storeVariable (TempVariable index) = do
|
||||||
temporaryRegisters' <- Allocator $ asks $ getField @"temporaryRegisters"
|
temporaryRegisters' <- Allocator $ lift $ asks $ getField @"temporaryRegisters"
|
||||||
pure $ RegisterStore
|
maybe (Allocator $ throwE AllocationError) (pure . RegisterStore)
|
||||||
$ temporaryRegisters' !! fromIntegral index
|
$ temporaryRegisters' !? fromIntegral index
|
||||||
storeVariable (LocalVariable index) = do
|
storeVariable (LocalVariable index) = do
|
||||||
temporaryRegisters' <- Allocator $ asks $ getField @"temporaryRegisters"
|
temporaryRegisters' <- Allocator $ lift $ asks $ getField @"temporaryRegisters"
|
||||||
Allocator $ lift $ modify' $ MachineState . (+ 4) . getField @"stackSize"
|
Allocator $ lift $ lift $ modify' $ MachineState . (+ 4) . getField @"stackSize"
|
||||||
pure $ StackStore (index * 4)
|
maybe (Allocator $ throwE AllocationError) (pure . StackStore (fromIntegral (succ index) * (-4)))
|
||||||
$ temporaryRegisters' !! pred (length temporaryRegisters' - fromIntegral index)
|
$ temporaryRegisters' !? pred (length temporaryRegisters' - fromIntegral index)
|
||||||
|
storeVariable (ParameterVariable index) = do
|
||||||
|
temporaryRegisters' <- Allocator $ lift $ asks $ getField @"temporaryRegisters"
|
||||||
|
maybe (Allocator $ throwE AllocationError) (pure . StackStore (fromIntegral index * 4))
|
||||||
|
$ temporaryRegisters' !? fromIntegral index
|
||||||
|
@ -19,13 +19,14 @@ instance Show Label
|
|||||||
where
|
where
|
||||||
show (Label label) = '.' : Text.unpack label
|
show (Label label) = '.' : Text.unpack label
|
||||||
|
|
||||||
data Variable = TempVariable Word32 | LocalVariable Word32
|
data Variable = TempVariable Word32 | LocalVariable Word32 | ParameterVariable Word32
|
||||||
deriving Eq
|
deriving Eq
|
||||||
|
|
||||||
instance Show Variable
|
instance Show Variable
|
||||||
where
|
where
|
||||||
show (LocalVariable variable) = '@' : show variable
|
show (LocalVariable variable) = '@' : show variable
|
||||||
show (TempVariable variable) = '$' : show variable
|
show (TempVariable variable) = '$' : show variable
|
||||||
|
show (ParameterVariable variable) = '%' : show variable
|
||||||
|
|
||||||
data Operand v
|
data Operand v
|
||||||
= IntOperand Int32
|
= IntOperand Int32
|
||||||
|
@ -71,7 +71,7 @@ data Statement
|
|||||||
= EmptyStatement
|
= EmptyStatement
|
||||||
| IfStatement Condition Statement (Maybe Statement)
|
| IfStatement Condition Statement (Maybe Statement)
|
||||||
| AssignmentStatement VariableAccess Expression
|
| AssignmentStatement VariableAccess Expression
|
||||||
-- | WhileStatement Condition Statement
|
| WhileStatement Condition Statement
|
||||||
| CompoundStatement [Statement]
|
| CompoundStatement [Statement]
|
||||||
| CallStatement Identifier [Expression]
|
| CallStatement Identifier [Expression]
|
||||||
deriving Eq
|
deriving Eq
|
||||||
@ -86,8 +86,8 @@ instance Show Statement
|
|||||||
]
|
]
|
||||||
show (AssignmentStatement lhs rhs) =
|
show (AssignmentStatement lhs rhs) =
|
||||||
concat [show lhs, " := ", show rhs, ";"]
|
concat [show lhs, " := ", show rhs, ";"]
|
||||||
{-show (WhileStatement expression statement) =
|
show (WhileStatement expression statement) =
|
||||||
concat ["while (", show expression, ") ", show statement, ";"]-}
|
concat ["while (", show expression, ") ", show statement, ";"]
|
||||||
show (CompoundStatement statements) =
|
show (CompoundStatement statements) =
|
||||||
concat ["{\n", unlines (show <$> statements), " }"]
|
concat ["{\n", unlines (show <$> statements), " }"]
|
||||||
show (CallStatement name parameters) = show name <> "("
|
show (CallStatement name parameters) = show name <> "("
|
||||||
|
@ -175,9 +175,9 @@ statement globalTable (AST.IfStatement ifCondition ifStatement elseStatement)
|
|||||||
statement globalTable (AST.AssignmentStatement lvalue rvalue)
|
statement globalTable (AST.AssignmentStatement lvalue rvalue)
|
||||||
= variableAccess globalTable lvalue
|
= variableAccess globalTable lvalue
|
||||||
>> expression globalTable rvalue
|
>> expression globalTable rvalue
|
||||||
--statement globalTable (AST.WhileStatement whileCondition loop)
|
statement globalTable (AST.WhileStatement whileCondition loop)
|
||||||
-- = condition globalTable whileCondition
|
= condition globalTable whileCondition
|
||||||
-- >> statement globalTable loop
|
>> statement globalTable loop
|
||||||
|
|
||||||
condition :: SymbolTable -> AST.Condition -> NameAnalysis ()
|
condition :: SymbolTable -> AST.Condition -> NameAnalysis ()
|
||||||
condition globalTable (AST.EqualCondition lhs rhs)
|
condition globalTable (AST.EqualCondition lhs rhs)
|
||||||
|
@ -188,7 +188,7 @@ statementP
|
|||||||
<|> ifElseP
|
<|> ifElseP
|
||||||
<|> CompoundStatement <$> blockP (many statementP)
|
<|> CompoundStatement <$> blockP (many statementP)
|
||||||
<|> try assignmentP
|
<|> try assignmentP
|
||||||
-- <|> try whileP
|
<|> try whileP
|
||||||
<|> callP
|
<|> callP
|
||||||
<?> "statement"
|
<?> "statement"
|
||||||
where
|
where
|
||||||
@ -200,9 +200,9 @@ statementP
|
|||||||
<$> (symbol "if" *> parensP conditionP)
|
<$> (symbol "if" *> parensP conditionP)
|
||||||
<*> statementP
|
<*> statementP
|
||||||
<*> optional (symbol "else" *> statementP)
|
<*> optional (symbol "else" *> statementP)
|
||||||
{-whileP = WhileStatement
|
whileP = WhileStatement
|
||||||
<$> (symbol "while" *> parensP conditionP)
|
<$> (symbol "while" *> parensP conditionP)
|
||||||
<*> statementP -}
|
<*> statementP
|
||||||
assignmentP = AssignmentStatement
|
assignmentP = AssignmentStatement
|
||||||
<$> variableAccessP
|
<$> variableAccessP
|
||||||
<* symbol ":="
|
<* symbol ":="
|
||||||
|
@ -101,11 +101,11 @@ statement globalTable = \case
|
|||||||
$ TypeAnalysis $ lift $ throwE $ InvalidConditionTypeError lhsType
|
$ TypeAnalysis $ lift $ throwE $ InvalidConditionTypeError lhsType
|
||||||
unless (rhsType == intType)
|
unless (rhsType == intType)
|
||||||
$ TypeAnalysis $ lift $ throwE $ InvalidConditionTypeError rhsType
|
$ TypeAnalysis $ lift $ throwE $ InvalidConditionTypeError rhsType
|
||||||
{- AST.WhileStatement whileCondition whileStatement -> do
|
AST.WhileStatement whileCondition whileStatement -> do
|
||||||
conditionType <- condition globalTable whileCondition
|
conditionType <- condition globalTable whileCondition
|
||||||
unless (conditionType == booleanType)
|
unless (conditionType == booleanType)
|
||||||
$ TypeAnalysis $ lift $ throwE $ InvalidConditionTypeError conditionType
|
$ TypeAnalysis $ lift $ throwE $ InvalidConditionTypeError conditionType
|
||||||
statement globalTable whileStatement -}
|
statement globalTable whileStatement
|
||||||
AST.IfStatement ifCondition ifStatement elseStatement -> do
|
AST.IfStatement ifCondition ifStatement elseStatement -> do
|
||||||
conditionType <- condition globalTable ifCondition
|
conditionType <- condition globalTable ifCondition
|
||||||
unless (conditionType == booleanType)
|
unless (conditionType == booleanType)
|
||||||
|
@ -71,14 +71,16 @@ declaration
|
|||||||
:: SymbolTable
|
:: SymbolTable
|
||||||
-> AST.Declaration
|
-> AST.Declaration
|
||||||
-> Glue (Maybe (AST.Identifier, Vector (Quadruple Variable)))
|
-> Glue (Maybe (AST.Identifier, Vector (Quadruple Variable)))
|
||||||
declaration globalTable (AST.ProcedureDeclaration procedureName _ variableDeclarations statements)
|
declaration globalTable (AST.ProcedureDeclaration procedureName parameters variableDeclarations statements)
|
||||||
= traverse_ registerVariable variableDeclarations
|
= traverseWithIndex registerVariable variableDeclarations
|
||||||
|
>> traverseWithIndex registerParameter parameters
|
||||||
>> nameQuadruplesTuple <$> traverse (statement globalTable) statements
|
>> nameQuadruplesTuple <$> traverse (statement globalTable) statements
|
||||||
where
|
where
|
||||||
registerVariable (AST.VariableDeclaration identifier _) = do
|
traverseWithIndex f = traverse_ (uncurry f) . zip [0..]
|
||||||
currentCounter <- fmap (fromIntegral . HashMap.size)
|
registerParameter index (AST.Parameter identifier _ _) =
|
||||||
$ Glue $ gets $ getField @"localMap"
|
Glue $ modify' $ modifier identifier $ ParameterVariable index
|
||||||
Glue $ modify' $ modifier identifier $ LocalVariable currentCounter
|
registerVariable index (AST.VariableDeclaration identifier _) =
|
||||||
|
Glue $ modify' $ modifier identifier $ LocalVariable index
|
||||||
modifier identifier currentCounter generator = generator
|
modifier identifier currentCounter generator = generator
|
||||||
{ localMap = HashMap.insert identifier currentCounter
|
{ localMap = HashMap.insert identifier currentCounter
|
||||||
$ getField @"localMap" generator
|
$ getField @"localMap" generator
|
||||||
@ -131,7 +133,7 @@ statement localTable (AST.AssignmentStatement variableAccess' assignee) = do
|
|||||||
. AssignQuadruple rhsOperand
|
. AssignQuadruple rhsOperand
|
||||||
<$> lookupLocal identifier
|
<$> lookupLocal identifier
|
||||||
pure $ rhsStatements <> lhsStatements
|
pure $ rhsStatements <> lhsStatements
|
||||||
{- statement localTable (AST.WhileStatement whileCondition whileStatement) = do
|
statement localTable (AST.WhileStatement whileCondition whileStatement) = do
|
||||||
(conditionStatements, jumpConstructor) <- condition localTable whileCondition
|
(conditionStatements, jumpConstructor) <- condition localTable whileCondition
|
||||||
startLabel <- createLabel
|
startLabel <- createLabel
|
||||||
endLabel <- createLabel
|
endLabel <- createLabel
|
||||||
@ -141,7 +143,7 @@ statement localTable (AST.AssignmentStatement variableAccess' assignee) = do
|
|||||||
<> conditionStatements
|
<> conditionStatements
|
||||||
<> Vector.fromList [jumpConstructor startLabel, GoToQuadruple endLabel, LabelQuadruple startLabel]
|
<> Vector.fromList [jumpConstructor startLabel, GoToQuadruple endLabel, LabelQuadruple startLabel]
|
||||||
<> whileStatements
|
<> whileStatements
|
||||||
<> Vector.fromList [GoToQuadruple conditionLabel, LabelQuadruple endLabel] -}
|
<> Vector.fromList [GoToQuadruple conditionLabel, LabelQuadruple endLabel]
|
||||||
|
|
||||||
createTemporary :: Glue Variable
|
createTemporary :: Glue Variable
|
||||||
createTemporary = do
|
createTemporary = do
|
||||||
|
@ -98,7 +98,7 @@ generateRiscV = flip evalState 0
|
|||||||
|
|
||||||
quadruple :: Word32 -> RiscVQuadruple -> RiscVGenerator (Vector Statement)
|
quadruple :: Word32 -> RiscVQuadruple -> RiscVGenerator (Vector Statement)
|
||||||
quadruple stackSize StartQuadruple =
|
quadruple stackSize StartQuadruple =
|
||||||
let totalStackSize = stackSize + 4
|
let totalStackSize = stackSize + 8
|
||||||
in pure $ Vector.fromList
|
in pure $ Vector.fromList
|
||||||
[ Instruction (RiscV.BaseInstruction RiscV.OpImm $ RiscV.I RiscV.SP RiscV.ADDI RiscV.SP (negate totalStackSize))
|
[ Instruction (RiscV.BaseInstruction RiscV.OpImm $ RiscV.I RiscV.SP RiscV.ADDI RiscV.SP (negate totalStackSize))
|
||||||
, Instruction (RiscV.BaseInstruction RiscV.Store $ RiscV.S 0 RiscV.SW RiscV.SP RiscV.S0)
|
, Instruction (RiscV.BaseInstruction RiscV.Store $ RiscV.S 0 RiscV.SW RiscV.SP RiscV.S0)
|
||||||
@ -106,7 +106,7 @@ quadruple stackSize StartQuadruple =
|
|||||||
, Instruction (RiscV.BaseInstruction RiscV.OpImm $ RiscV.I RiscV.S0 RiscV.ADDI RiscV.SP totalStackSize)
|
, Instruction (RiscV.BaseInstruction RiscV.OpImm $ RiscV.I RiscV.S0 RiscV.ADDI RiscV.SP totalStackSize)
|
||||||
]
|
]
|
||||||
quadruple stackSize StopQuadruple =
|
quadruple stackSize StopQuadruple =
|
||||||
let totalStackSize = stackSize + 4
|
let totalStackSize = stackSize + 8
|
||||||
in pure $ Vector.fromList
|
in pure $ Vector.fromList
|
||||||
[ Instruction (RiscV.BaseInstruction RiscV.Load $ RiscV.I RiscV.S0 RiscV.LW RiscV.SP 0)
|
[ Instruction (RiscV.BaseInstruction RiscV.Load $ RiscV.I RiscV.S0 RiscV.LW RiscV.SP 0)
|
||||||
, Instruction (RiscV.BaseInstruction RiscV.Load $ RiscV.I RiscV.RA RiscV.LW RiscV.SP 4)
|
, Instruction (RiscV.BaseInstruction RiscV.Load $ RiscV.I RiscV.RA RiscV.LW RiscV.SP 4)
|
||||||
@ -438,7 +438,7 @@ loadFromStore (RegisterStore register) = (register, mempty)
|
|||||||
loadFromStore (StackStore offset register) =
|
loadFromStore (StackStore offset register) =
|
||||||
let loadInstruction = Instruction
|
let loadInstruction = Instruction
|
||||||
$ RiscV.BaseInstruction RiscV.Load
|
$ RiscV.BaseInstruction RiscV.Load
|
||||||
$ RiscV.I register RiscV.LW RiscV.S0 offset
|
$ RiscV.I register RiscV.LW RiscV.S0 (fromIntegral offset)
|
||||||
in (register, Vector.singleton loadInstruction)
|
in (register, Vector.singleton loadInstruction)
|
||||||
|
|
||||||
storeToStore :: RiscVStore -> (RiscV.XRegister, Vector Statement)
|
storeToStore :: RiscVStore -> (RiscV.XRegister, Vector Statement)
|
||||||
@ -446,5 +446,5 @@ storeToStore (RegisterStore register) = (register, mempty)
|
|||||||
storeToStore (StackStore offset register) =
|
storeToStore (StackStore offset register) =
|
||||||
let storeInstruction = Instruction
|
let storeInstruction = Instruction
|
||||||
$ RiscV.BaseInstruction RiscV.Store
|
$ RiscV.BaseInstruction RiscV.Store
|
||||||
$ RiscV.S offset RiscV.SW RiscV.S0 register
|
$ RiscV.S (fromIntegral offset) RiscV.SW RiscV.S0 register
|
||||||
in (register, Vector.singleton storeInstruction)
|
in (register, Vector.singleton storeInstruction)
|
||||||
|
@ -25,6 +25,7 @@ import Control.Exception (IOException, catch)
|
|||||||
-- 3 - Parse error.
|
-- 3 - Parse error.
|
||||||
-- 4 - Name analysis error.
|
-- 4 - Name analysis error.
|
||||||
-- 5 - Type error.
|
-- 5 - Type error.
|
||||||
|
-- 6 - Register allocation error.
|
||||||
|
|
||||||
main :: IO ()
|
main :: IO ()
|
||||||
main = execParser commandLine >>= withCommandLine
|
main = execParser commandLine >>= withCommandLine
|
||||||
@ -45,9 +46,9 @@ main = execParser commandLine >>= withCommandLine
|
|||||||
| Just typeError <- typeAnalysis symbolTable program =
|
| Just typeError <- typeAnalysis symbolTable program =
|
||||||
printAndExit 5 typeError
|
printAndExit 5 typeError
|
||||||
| otherwise =
|
| otherwise =
|
||||||
let instructions = generateRiscV
|
let makeObject = elfObject output . riscv32Elf . generateRiscV
|
||||||
$ allocate riscVConfiguration
|
in either (printAndExit 6) makeObject
|
||||||
$ glue symbolTable program
|
$ allocate riscVConfiguration
|
||||||
in elfObject output $ riscv32Elf instructions
|
$ glue symbolTable program
|
||||||
printAndExit :: Show b => forall a. Int -> b -> IO a
|
printAndExit :: Show b => forall a. Int -> b -> IO a
|
||||||
printAndExit failureCode e = print e >> exitWith (ExitFailure failureCode)
|
printAndExit failureCode e = print e >> exitWith (ExitFailure failureCode)
|
||||||
|
1
tests/expectations/print_after_loop.txt
Normal file
1
tests/expectations/print_after_loop.txt
Normal file
@ -0,0 +1 @@
|
|||||||
|
2
|
1
tests/expectations/print_in_proc.txt
Normal file
1
tests/expectations/print_in_proc.txt
Normal file
@ -0,0 +1 @@
|
|||||||
|
14
|
9
tests/vm/print_after_loop.elna
Normal file
9
tests/vm/print_after_loop.elna
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
proc main() {
|
||||||
|
var x: int;
|
||||||
|
|
||||||
|
x := 0;
|
||||||
|
while (x < 2) {
|
||||||
|
x := x + 1;
|
||||||
|
}
|
||||||
|
printi(x);
|
||||||
|
}
|
7
tests/vm/print_in_proc.elna
Normal file
7
tests/vm/print_in_proc.elna
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
proc print2(a: int) {
|
||||||
|
printi(a);
|
||||||
|
}
|
||||||
|
|
||||||
|
proc main() {
|
||||||
|
print2(14);
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user