752 lines
27 KiB
C#
752 lines
27 KiB
C#
// ----------------------------------------------------------------------------
|
|
//
|
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
|
//
|
|
// ----------------------------------------------------------------------------
|
|
|
|
using System;
|
|
using System.Collections;
|
|
using System.Text.RegularExpressions;
|
|
using System.Text;
|
|
using Microsoft.Singularity;
|
|
using Microsoft.Singularity.Io;
|
|
using Microsoft.Contracts;
|
|
|
|
namespace Microsoft.Singularity.Applications
|
|
{
|
|
///
|
|
// Provides a reasonable script engine implementation.
|
|
// This engine is composed of two distinct parts: the parser
|
|
// and the interpreter. Parser.cs and ScriptParser.cs provide
|
|
// the parsing functionality. Parser.cs is the general handwritten slr
|
|
// driver while ScriptParser contains the automatically generated
|
|
// parse tables and semantic action functions for productions
|
|
// and lex operations of this specific scripting language. This file
|
|
// provides the interpretation mechanism that runs over an ast produced
|
|
// by the parser.
|
|
//
|
|
class ScriptEngine
|
|
{
|
|
private String script;
|
|
private Block parsedScript;
|
|
public const String LAST_COMMAND_EXIT_SYMBOL = "?";
|
|
public const String NUM_ARGUMENTS_SYM = "#";
|
|
public delegate int CommandLineRunner(ShellControl! shellControl,
|
|
String[] commandLine,
|
|
bool isBackground);
|
|
|
|
private CommandLineRunner runner;
|
|
|
|
|
|
public static int Run(String script, ShellControl! shellControl, CommandLineRunner runner)
|
|
{
|
|
return Run(script, shellControl, runner, null);
|
|
}
|
|
|
|
public static int Run(String script, ShellControl! shellControl,
|
|
CommandLineRunner runner, String[] arguments)
|
|
{
|
|
ScriptEngine engine = new ScriptEngine(script, runner);
|
|
try {
|
|
engine.Parse();
|
|
}
|
|
catch (Parser.ParseException e) {
|
|
Console.WriteLine(e.Message);
|
|
return -1;
|
|
}
|
|
catch (Exception e) {
|
|
Console.WriteLine(e.Message);
|
|
return -1;
|
|
}
|
|
|
|
try {
|
|
return engine.Run(shellControl, arguments);
|
|
}
|
|
catch (Interpretation.InterpretationException e) {
|
|
Console.WriteLine(e.Message);
|
|
}
|
|
catch (ScriptException e) {
|
|
Console.WriteLine(e.Message);
|
|
}
|
|
catch (Exception e) {
|
|
Console.WriteLine(e.Message);
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
|
|
private ScriptEngine(String script, CommandLineRunner runner)
|
|
{
|
|
this.script = script;
|
|
this.runner = runner;
|
|
}
|
|
|
|
private void Parse()
|
|
{
|
|
ScriptParser parser = new ScriptParser();
|
|
parsedScript = (Block)parser.Parse(script);
|
|
|
|
}
|
|
|
|
private int Run(ShellControl! shellControl, String[] arguments)
|
|
{
|
|
Interpretation interp = new Interpretation(runner, arguments);
|
|
parsedScript.Interpret(shellControl, interp);
|
|
return interp.ExitCode;
|
|
}
|
|
|
|
public class ScriptException : Exception
|
|
{
|
|
public ScriptException(String message) : base(message) { }
|
|
}
|
|
|
|
public class Interpretation
|
|
{
|
|
public static readonly object! undefinedObj = new Object();
|
|
private Hashtable! symbolTable;
|
|
private bool exit = false;
|
|
|
|
[NotDelayed]
|
|
public Interpretation(CommandLineRunner runner, String[] arguments)
|
|
{
|
|
this.runner = runner;
|
|
symbolTable = new Hashtable();
|
|
base();
|
|
int i = 0;
|
|
if (arguments != null) {
|
|
for (; i < arguments.Length; ++i) {
|
|
SetSymbol(i.ToString(), arguments[i]);
|
|
}
|
|
}
|
|
SetSymbol(NUM_ARGUMENTS_SYM, i);
|
|
}
|
|
|
|
public int ExitCode
|
|
{
|
|
get {
|
|
Object exitCode = LookupSymbol(LAST_COMMAND_EXIT_SYMBOL);
|
|
if (exitCode == null) {
|
|
return 0;
|
|
}
|
|
return (Int32) exitCode;
|
|
}
|
|
set { SetSymbol(LAST_COMMAND_EXIT_SYMBOL, value); }
|
|
}
|
|
|
|
public bool Exit
|
|
{
|
|
get { return exit; }
|
|
set { exit = value; }
|
|
}
|
|
|
|
public CommandLineRunner runner;
|
|
|
|
public object! LookupSymbol(string! symbol)
|
|
{
|
|
Object value = symbolTable[symbol];
|
|
if (value == null) {
|
|
return undefinedObj;
|
|
}
|
|
return value;
|
|
}
|
|
public void SetSymbol(string! key, Object value)
|
|
{
|
|
symbolTable[key] = value;
|
|
}
|
|
|
|
public class InterpretationException : Exception
|
|
{
|
|
public InterpretationException(String message) : base(message) { }
|
|
}
|
|
|
|
public class UndefinedVariableException : InterpretationException
|
|
{
|
|
public UndefinedVariableException(String variable)
|
|
: base("Use of undefined variable: " + variable) { }
|
|
}
|
|
|
|
public class TypeConversionException : InterpretationException
|
|
{
|
|
public TypeConversionException(String value, String type)
|
|
: base("Could not convert " + value + " to type: " + type) { }
|
|
}
|
|
|
|
public class UnknownTypeException : InterpretationException
|
|
{
|
|
public UnknownTypeException(String type)
|
|
: base("Unknown type" + type) { }
|
|
}
|
|
|
|
public class DivideByZeroException : InterpretationException
|
|
{
|
|
public DivideByZeroException() : base("Division by zero undefined.") { }
|
|
}
|
|
|
|
|
|
}
|
|
public abstract class Element { }
|
|
|
|
public abstract class Expression : Element
|
|
{
|
|
public abstract object! GetValue(Interpretation! interp);
|
|
public abstract string! StringValue(Interpretation! interp);
|
|
public abstract int IntValue(Interpretation! interp);
|
|
public abstract bool BoolValue(Interpretation! interp);
|
|
public bool IsIntLike() { return true; }
|
|
public bool IsBoolLike() { return true; }
|
|
public bool IsStringLike() { return true; }
|
|
}
|
|
|
|
public class VariableReference : Expression
|
|
{
|
|
String variable;
|
|
public VariableReference(String variable)
|
|
{
|
|
this.variable = variable;
|
|
}
|
|
public override object! GetValue(Interpretation! interp)
|
|
{
|
|
object value = interp.LookupSymbol(variable);
|
|
if (value == Interpretation.undefinedObj)
|
|
throw new Interpretation.UndefinedVariableException(variable);
|
|
return value;
|
|
}
|
|
|
|
public override string! StringValue(Interpretation! interp)
|
|
{
|
|
object value = GetValue(interp);
|
|
return value.ToString();
|
|
}
|
|
|
|
public override int IntValue(Interpretation! interp)
|
|
{
|
|
Object value = GetValue(interp);
|
|
if (value is String) {
|
|
return Int32.Parse((String)value);
|
|
}
|
|
else if (value is Int32) {
|
|
return (int)value;
|
|
}
|
|
else if (value is bool) {
|
|
return ((bool)value) ? 1 : 0;
|
|
}
|
|
throw new Interpretation.UnknownTypeException(value.GetType().ToString());
|
|
}
|
|
|
|
public override bool BoolValue(Interpretation! interp)
|
|
{
|
|
Object value = GetValue(interp);
|
|
if (value is String) {
|
|
try {
|
|
return Boolean.Parse((String)value);
|
|
}
|
|
catch (FormatException) {
|
|
throw new Interpretation.TypeConversionException((String)value, "bool");
|
|
}
|
|
|
|
}
|
|
else if (value is Int32) {
|
|
|
|
return ((int)value) == 0 ? true : false;
|
|
}
|
|
else if (value is Boolean) {
|
|
return (bool)value;
|
|
}
|
|
throw new Interpretation.UnknownTypeException(value.GetType().ToString());
|
|
}
|
|
}
|
|
|
|
public abstract class IntegerExpression : Expression
|
|
{
|
|
public override object! GetValue(Interpretation! interp) { return IntValue(interp); }
|
|
public override string! StringValue(Interpretation! interp)
|
|
{
|
|
int value = IntValue(interp);
|
|
return value.ToString();
|
|
}
|
|
public override bool BoolValue(Interpretation! interp)
|
|
{
|
|
int value = IntValue(interp);
|
|
return value == 0 ? true : false;
|
|
}
|
|
}
|
|
|
|
public class IntegerLiteral : IntegerExpression
|
|
{
|
|
private int value;
|
|
public IntegerLiteral(int value)
|
|
{
|
|
this.value = value;
|
|
}
|
|
public override int IntValue(Interpretation! interp) { return value; }
|
|
}
|
|
public abstract class IntegerBinaryExpression : IntegerExpression
|
|
{
|
|
public Expression left;
|
|
public Expression right;
|
|
protected IntegerBinaryExpression(Expression left, Expression right)
|
|
{
|
|
this.left = left; this.right = right;
|
|
}
|
|
}
|
|
public class Add : IntegerBinaryExpression
|
|
{
|
|
public Add(Expression left, Expression right) : base(left, right) { }
|
|
public override int IntValue(Interpretation! interp)
|
|
{
|
|
return left.IntValue(interp) + right.IntValue(interp);
|
|
}
|
|
}
|
|
public class Subtract : IntegerBinaryExpression
|
|
{
|
|
public Subtract(Expression left, Expression right) : base(left, right) { }
|
|
public override int IntValue(Interpretation! interp)
|
|
{
|
|
return left.IntValue(interp) - right.IntValue(interp);
|
|
}
|
|
}
|
|
|
|
public class Multiply : IntegerBinaryExpression
|
|
{
|
|
public Multiply(Expression left, Expression right) : base(left, right) { }
|
|
public override int IntValue(Interpretation! interp)
|
|
{
|
|
return left.IntValue(interp) * right.IntValue(interp);
|
|
}
|
|
}
|
|
|
|
public class Divide : IntegerBinaryExpression
|
|
{
|
|
public Divide(Expression left, Expression right) : base(left, right) { }
|
|
public override int IntValue(Interpretation! interp)
|
|
{
|
|
int leftVal = left.IntValue(interp);
|
|
int rightVal = right.IntValue(interp);
|
|
if (rightVal == 0) {
|
|
throw new DivideByZeroException();
|
|
}
|
|
return leftVal / rightVal;
|
|
|
|
}
|
|
}
|
|
|
|
public class Mod : IntegerBinaryExpression
|
|
{
|
|
public Mod(Expression left, Expression right) : base(left, right) { }
|
|
public override int IntValue(Interpretation! interp)
|
|
{
|
|
return left.IntValue(interp) % right.IntValue(interp);
|
|
}
|
|
}
|
|
public abstract class BooleanExpression : Expression
|
|
{
|
|
public override object! GetValue(Interpretation! interp) { return BoolValue(interp); }
|
|
public override int IntValue(Interpretation! interp)
|
|
{
|
|
bool value = BoolValue(interp);
|
|
return value ? 1 : 0;
|
|
}
|
|
public override string! StringValue(Interpretation! interp)
|
|
{
|
|
bool value = BoolValue(interp);
|
|
return value.ToString();
|
|
}
|
|
}
|
|
|
|
public class BooleanLiteral : BooleanExpression
|
|
{
|
|
bool value;
|
|
public BooleanLiteral(bool value)
|
|
{
|
|
this.value = value;
|
|
}
|
|
public override bool BoolValue(Interpretation! interp) { return value; }
|
|
|
|
}
|
|
public abstract class BooleanBinaryExpression : BooleanExpression
|
|
{
|
|
protected Expression left;
|
|
protected Expression right;
|
|
public BooleanBinaryExpression(Expression left, Expression right)
|
|
{
|
|
this.left = left; this.right = right;
|
|
}
|
|
protected int CompareOperands(Interpretation! interp)
|
|
{
|
|
return CompareTo(left.GetValue(interp), right, interp);
|
|
}
|
|
private static int CompareTo(Object left, Expression e, Interpretation! interp)
|
|
{
|
|
if (left == null) {
|
|
if (e == null) {
|
|
return 0;
|
|
}
|
|
return -1;
|
|
}
|
|
if (e == null) {
|
|
return 1;
|
|
}
|
|
if (left is String) {
|
|
return ((String)left).CompareTo(e.StringValue(interp));
|
|
}
|
|
else if (left is int) {
|
|
return ((int)left).CompareTo(e.IntValue(interp));
|
|
}
|
|
else if (left is Boolean) {
|
|
return ((Boolean)left).CompareTo(e.BoolValue(interp));
|
|
}
|
|
throw new Interpretation.UnknownTypeException("unsupported type");
|
|
}
|
|
}
|
|
public class And : BooleanBinaryExpression
|
|
{
|
|
public And(Expression left, Expression right) : base(left, right) { }
|
|
|
|
public override bool BoolValue(Interpretation! interp)
|
|
{
|
|
return left.BoolValue(interp) && right.BoolValue(interp);
|
|
}
|
|
}
|
|
|
|
public class Or : BooleanBinaryExpression
|
|
{
|
|
public Or(Expression left, Expression right) : base(left, right) { }
|
|
public override bool BoolValue(Interpretation! interp)
|
|
{
|
|
return left.BoolValue(interp) || right.BoolValue(interp);
|
|
}
|
|
}
|
|
|
|
public class Less : BooleanBinaryExpression
|
|
{
|
|
public Less(Expression left, Expression right) : base(left, right) { }
|
|
public override bool BoolValue(Interpretation! interp)
|
|
{
|
|
int compare = CompareOperands(interp);
|
|
return compare < 0;
|
|
}
|
|
}
|
|
|
|
public class LessEqual : BooleanBinaryExpression
|
|
{
|
|
public LessEqual(Expression left, Expression right) : base(left, right) { }
|
|
public override bool BoolValue(Interpretation! interp)
|
|
{
|
|
int compare = CompareOperands(interp);
|
|
return compare < 0 || compare == 0;
|
|
}
|
|
}
|
|
|
|
public class Equal : BooleanBinaryExpression
|
|
{
|
|
public Equal(Expression left, Expression right) : base(left, right) { }
|
|
public override bool BoolValue(Interpretation! interp)
|
|
{
|
|
int compare = CompareOperands(interp);
|
|
return compare == 0;
|
|
}
|
|
}
|
|
|
|
public class Greater : BooleanBinaryExpression
|
|
{
|
|
public Greater(Expression left, Expression right) : base(left, right) { }
|
|
public override bool BoolValue(Interpretation! interp)
|
|
{
|
|
int compare = CompareOperands(interp);
|
|
return compare > 0;
|
|
}
|
|
}
|
|
|
|
public class GreaterEqual : BooleanBinaryExpression
|
|
{
|
|
public GreaterEqual(Expression left, Expression right) : base(left, right) { }
|
|
public override bool BoolValue(Interpretation! interp)
|
|
{
|
|
int compare = CompareOperands(interp);
|
|
return compare == 0 || compare > 0;
|
|
}
|
|
}
|
|
|
|
|
|
public class Negate : BooleanExpression
|
|
{
|
|
Expression expr;
|
|
public Negate(Expression expr)
|
|
{
|
|
this.expr = expr;
|
|
}
|
|
public override bool BoolValue(Interpretation! interp)
|
|
{
|
|
return !expr.BoolValue(interp);
|
|
}
|
|
}
|
|
|
|
public abstract class StringExpression : Expression
|
|
{
|
|
public override object! GetValue(Interpretation! interp)
|
|
{
|
|
return StringValue(interp);
|
|
}
|
|
public override int IntValue(Interpretation! interp)
|
|
{
|
|
String value = StringValue(interp);
|
|
try {
|
|
return Int32.Parse(value);
|
|
}
|
|
catch (FormatException) {
|
|
throw new Interpretation.TypeConversionException(value, "String");
|
|
}
|
|
}
|
|
|
|
public override bool BoolValue(Interpretation! interp)
|
|
{
|
|
String value = StringValue(interp);
|
|
try {
|
|
return Boolean.Parse(value);
|
|
}
|
|
catch (FormatException) {
|
|
throw new Interpretation.TypeConversionException(value, "String");
|
|
}
|
|
}
|
|
}
|
|
private static Regex whitespace = new Regex(@"\s+");
|
|
|
|
public class StringInterpolation : StringExpression
|
|
{
|
|
private ArrayList interpolationElems;
|
|
private static Regex escaped_dollar = new Regex(@"\\\$");
|
|
|
|
private static String[]! Split(string! interpolation)
|
|
{
|
|
ArrayList chunks = new ArrayList();
|
|
char last = (char) -1;
|
|
int lastIndex = 0;
|
|
for (int i = 0; i < interpolation.Length; last = interpolation[i], ++i) {
|
|
if (interpolation[i] == '$' && last != '\\') {
|
|
chunks.Add(interpolation.Substring(lastIndex, i - lastIndex));
|
|
lastIndex = i + 1;
|
|
}
|
|
}
|
|
chunks.Add((lastIndex < interpolation.Length - 1 ? interpolation.Substring(lastIndex) : ""));
|
|
String[] strings = new string[chunks.Count];
|
|
chunks.CopyTo(strings);
|
|
return strings;
|
|
}
|
|
|
|
[NotDelayed]
|
|
public StringInterpolation(string! interpolation)
|
|
{
|
|
interpolationElems = new ArrayList();
|
|
String[] split = Split(interpolation);
|
|
if (split[0] != "") {
|
|
//replace escaped $ and add
|
|
interpolationElems.Add(new StringLiteral(escaped_dollar.Replace(split[0], "$")));
|
|
}
|
|
for (int i = 1; i < split.Length; ++i) {
|
|
string var, rest;
|
|
string! split_i = (!)split[i];
|
|
if (split_i[0] == '{') {
|
|
int index = split_i.IndexOf('}');
|
|
if (index == -1) {
|
|
throw new ScriptException("Unmatched left brace in " + interpolation);
|
|
}
|
|
var = split_i.Substring(1, index - 1);
|
|
rest = index + 1 < split_i.Length ? split_i.Substring(index + 1) : "";
|
|
}
|
|
else {
|
|
Match m = whitespace.Match(split_i);
|
|
assert m != null;
|
|
int index = m.Success ? m.Index : split_i.Length;
|
|
var = split_i.Substring(0, index);
|
|
rest = index + 1 < split_i.Length ? split_i.Substring(index + 1) : "";
|
|
}
|
|
//didn't replace escaped dollar signs in variable, bad syntax anyway.
|
|
interpolationElems.Add(new VariableReference(var));
|
|
if (rest != "") {
|
|
interpolationElems.Add(new StringLiteral(escaped_dollar.Replace(rest, "$")));
|
|
|
|
}
|
|
}
|
|
}
|
|
|
|
public override string! StringValue(Interpretation! interp)
|
|
{
|
|
StringBuilder sb = new StringBuilder();
|
|
foreach (Expression! elem in interpolationElems) {
|
|
sb.Append(elem.StringValue(interp));
|
|
}
|
|
return sb.ToString();
|
|
}
|
|
}
|
|
public class StringLiteral : StringExpression
|
|
{
|
|
private string literal;
|
|
public StringLiteral(String literal)
|
|
{
|
|
this.literal = literal;
|
|
}
|
|
public override string! StringValue(Interpretation! interp)
|
|
{
|
|
return literal;
|
|
|
|
}
|
|
}
|
|
|
|
public class Concat : StringExpression
|
|
{
|
|
private Expression left;
|
|
private Expression right;
|
|
public Concat(Expression left, Expression right)
|
|
{
|
|
this.left = left; this.right = right;
|
|
}
|
|
|
|
public override string! StringValue(Interpretation! interp)
|
|
{
|
|
return left.StringValue(interp) + right.StringValue(interp);
|
|
}
|
|
}
|
|
|
|
public abstract class Statement : Element
|
|
{
|
|
public abstract void Interpret(ShellControl! shellControl, Interpretation! interp);
|
|
}
|
|
|
|
public class Assign : Statement
|
|
{
|
|
String variable;
|
|
Expression expression;
|
|
public Assign(String variable, Expression expression)
|
|
{
|
|
this.variable = variable; this.expression = expression;
|
|
}
|
|
public override void Interpret(ShellControl! shellControl, Interpretation! interp)
|
|
{
|
|
Object value = expression.GetValue(interp);
|
|
if (interp != null)
|
|
interp.SetSymbol(variable, value);
|
|
}
|
|
}
|
|
public class If : Statement
|
|
{
|
|
Expression condition;
|
|
Block block;
|
|
public If(Expression condition, Block block)
|
|
{
|
|
this.condition = condition; this.block = block;
|
|
}
|
|
public override void Interpret(ShellControl! shellControl, Interpretation! interp)
|
|
{
|
|
if (condition.BoolValue(interp)) {
|
|
block.Interpret(shellControl, interp);
|
|
}
|
|
}
|
|
}
|
|
public class IfThenElse : Statement
|
|
{
|
|
Expression condition;
|
|
Block trueBlock;
|
|
Block falseBlock;
|
|
public IfThenElse(Expression condition, Block trueBlock, Block falseBlock)
|
|
{
|
|
this.condition = condition; this.trueBlock = trueBlock; this.falseBlock = falseBlock;
|
|
}
|
|
public override void Interpret(ShellControl! shellControl, Interpretation! interp)
|
|
{
|
|
if (condition.BoolValue(interp)) {
|
|
trueBlock.Interpret(shellControl, interp);
|
|
}
|
|
else {
|
|
falseBlock.Interpret(shellControl, interp);
|
|
}
|
|
}
|
|
}
|
|
|
|
public class While : Statement
|
|
{
|
|
Expression condition;
|
|
Block block;
|
|
public While(Expression condition, Block block)
|
|
{
|
|
this.condition = condition; this.block = block;
|
|
}
|
|
public override void Interpret(ShellControl! shellControl, Interpretation! interp)
|
|
{
|
|
while (condition.BoolValue(interp)) {
|
|
block.Interpret(shellControl, interp);
|
|
if (interp.Exit) break;
|
|
}
|
|
}
|
|
}
|
|
public class Block : Statement
|
|
{
|
|
ArrayList statements;
|
|
public Block(ArrayList statements)
|
|
{
|
|
this.statements = statements;
|
|
}
|
|
public override void Interpret(ShellControl! shellControl, Interpretation! interp)
|
|
{
|
|
foreach (Statement statement in statements) {
|
|
if (statement != null)
|
|
statement.Interpret(shellControl, interp);
|
|
if (interp.Exit) break;
|
|
}
|
|
}
|
|
public override string! ToString()
|
|
{
|
|
throw new ScriptException("The method or operation is not implemented.");
|
|
}
|
|
}
|
|
|
|
public class Command : Statement
|
|
{
|
|
public ArrayList commandLine;
|
|
bool async;
|
|
|
|
public Command(ArrayList commandLine) : this(commandLine, false) { }
|
|
public Command(ArrayList commandLine, bool async)
|
|
{
|
|
this.commandLine = commandLine;
|
|
this.async = async;
|
|
}
|
|
public override void Interpret(ShellControl! shellControl, Interpretation! interp)
|
|
{
|
|
String[] arguments = new String[commandLine.Count];
|
|
int i = 0;
|
|
//Console.WriteLine("Interpret Command");
|
|
foreach (Expression! e in commandLine) {
|
|
string arg = e.StringValue(interp);
|
|
//Console.WriteLine(" CMD[{0}]: {1}", i, arg);
|
|
arguments[i++] = arg;
|
|
}
|
|
if (i != 0 && ((!)arguments[0]).Trim() == "exit") {
|
|
if (arguments.Length >= 2) {
|
|
try {
|
|
int exitCode = Int32.Parse(arguments[1]);
|
|
interp.ExitCode = exitCode;
|
|
}
|
|
catch (FormatException) {
|
|
throw new Interpretation.TypeConversionException((String)arguments[1], "integer");
|
|
}
|
|
}
|
|
interp.Exit = true;
|
|
}
|
|
else if (false) {
|
|
//
|
|
//arguments[0].Trim() == "read") {
|
|
//string input = Console.ReadLine();
|
|
//String[] split = whitespace.Split(input);
|
|
//for (int j = 1, k = 0; j < arguments.Length && k < split.Length; ++j, ++k) {
|
|
//interp.SetSymbol(arguments[j], split[k]);
|
|
//
|
|
}
|
|
else {
|
|
int exitCode = interp.runner(shellControl, arguments, async);
|
|
interp.ExitCode = exitCode;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|