Created
August 7, 2021 17:17
-
-
Save jmcd/b0bbc391ae708cfe090d7e27a4e065a5 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
namespace OneHour | |
{ | |
using System; | |
using System.Collections.Generic; | |
using System.Linq; | |
using Xunit; | |
internal class Command | |
{ | |
public enum CommandKind | |
{ | |
SetVar, | |
GetVar, | |
PushVar, | |
Push, | |
Pop, | |
Add, | |
} | |
public static readonly Command Pop = new Command(CommandKind.Pop, default, default); | |
public static readonly Command Add = new Command(CommandKind.Add, default, default); | |
private Command(CommandKind kind, string? name, Value? value) | |
{ | |
Kind = kind; | |
Name = name; | |
Value = value; | |
} | |
public CommandKind Kind { get; } | |
public string? Name { get; } | |
public Value? Value { get; } | |
public static Command SetVar(string name, Value value) => new Command(CommandKind.SetVar, name, value); | |
public static Command GetVar(string name) => new Command(CommandKind.GetVar, name, default); | |
public static Command PushVar(string name) => new Command(CommandKind.PushVar, name, default); | |
public static Command Push(Value value) => new Command(CommandKind.Push, default, value); | |
} | |
internal class Value | |
{ | |
public enum ValueKind | |
{ | |
Nothing, | |
String, | |
Int, | |
} | |
public static readonly Value Nothing = new Value(ValueKind.Nothing); | |
private Value(ValueKind kind, long? intValue = default, string? stringValue = default) | |
{ | |
Kind = kind; | |
IntValue = intValue; | |
StringValue = stringValue; | |
} | |
public ValueKind Kind { get; } | |
public long? IntValue { get; } | |
public string? StringValue { get; } | |
public static Value String(string s) => new Value(ValueKind.String, default, s); | |
public static Value Int(long l) => new Value(ValueKind.Int, l); | |
} | |
internal enum Type { } | |
internal class EngineError | |
{ | |
public static readonly EngineError MismatchType = new EngineError(Kind.MismatchType); | |
public static readonly EngineError MismatchNumParams = new EngineError(Kind.MismatchNumParams); | |
public static readonly EngineError EmptyStack = new EngineError(Kind.EmptyStack); | |
private readonly Kind kind; | |
private EngineError(Kind kind, string? unknownCommand = default, string? missingVariableName = default) => this.kind = kind; | |
public static EngineError UnknownCommand(string unknownCommand) => new EngineError(Kind.UnknownCommand, unknownCommand); | |
public static EngineError MissingVariableName(string missingVariableName) => new EngineError(Kind.UnknownCommand, default, missingVariableName); | |
private enum Kind | |
{ | |
MismatchType, | |
MismatchNumParams, | |
UnknownCommand, | |
EmptyStack, | |
} | |
} | |
internal class Evaluator | |
{ | |
private readonly Stack<Value> stack = new(); | |
private readonly Dictionary<string, Value> vars = new(); | |
public Result<Value, EngineError> Evaluate(IList<Command> commands) | |
{ | |
var output = Result<Value, EngineError>.Ok(Value.Nothing); | |
foreach (var command in commands) | |
{ | |
switch (command.Kind) | |
{ | |
case Command.CommandKind.SetVar: | |
vars[command.Name!] = command.Value!; | |
break; | |
case Command.CommandKind.GetVar: | |
{ | |
if (vars.TryGetValue(command.Name!, out var value)) | |
{ | |
output = Result<Value, EngineError>.Ok(value); | |
} | |
else | |
{ | |
return Result<Value, EngineError>.Err(EngineError.MissingVariableName(command.Name!)); | |
} | |
} | |
break; | |
case Command.CommandKind.PushVar: | |
{ | |
if (vars.TryGetValue(command.Name!, out var value)) | |
{ | |
stack.Push(value); | |
} | |
else | |
{ | |
return Result<Value, EngineError>.Err(EngineError.MissingVariableName(command.Name!)); | |
} | |
} | |
break; | |
case Command.CommandKind.Push: | |
stack.Push(command.Value!); | |
break; | |
case Command.CommandKind.Pop: | |
output = Pop(); | |
break; | |
case Command.CommandKind.Add: | |
var lhs = Pop(); | |
if (lhs.ErrVal is object) | |
{ | |
return Result<Value, EngineError>.Err(lhs.ErrVal); | |
} | |
var rhs = Pop(); | |
if (rhs.ErrVal is object) | |
{ | |
return Result<Value, EngineError>.Err(rhs.ErrVal); | |
} | |
var result = Add(lhs.OkVal!, rhs.OkVal!); | |
if (result.ErrVal is object) | |
{ | |
return Result<Value, EngineError>.Err(result.ErrVal); | |
} | |
stack.Push(result.OkVal!); | |
break; | |
default: | |
throw new ArgumentOutOfRangeException(); | |
} | |
} | |
return output; | |
} | |
private static Result<Value, EngineError> Add(Value lhs, Value rhs) => | |
(lhs.Kind, rhs.Kind) switch | |
{ | |
(Value.ValueKind.Int, Value.ValueKind.Int) => Result<Value, EngineError>.Ok(Value.Int(lhs.IntValue!.Value + rhs.IntValue!.Value)), | |
(Value.ValueKind.String, Value.ValueKind.String) => Result<Value, EngineError>.Ok(Value.String(lhs.StringValue! + rhs.StringValue!)), | |
_ => Result<Value, EngineError>.Err(EngineError.MismatchType), | |
}; | |
private Result<Value, EngineError> Pop() => | |
stack.TryPop(out var value) ? Result<Value, EngineError>.Ok(value) : Result<Value, EngineError>.Err(EngineError.EmptyStack); | |
} | |
internal class Result<TOk, TErr> | |
{ | |
private Result(TOk? okVal, TErr? errVal) | |
{ | |
OkVal = okVal; | |
ErrVal = errVal; | |
} | |
public TErr? ErrVal { get; } | |
public TOk? OkVal { get; } | |
public static Result<TOk, TErr> Ok(TOk ok) => new(ok, default); | |
public static Result<TOk, TErr> Err(TErr err) => new(default, err); | |
} | |
internal static class ParseUtils | |
{ | |
public static Result<string, EngineError> ParseVarName(string varName) => Result<string, EngineError>.Ok(varName); | |
public static Result<Value, EngineError> ParseString(string val) | |
{ | |
if (val.StartsWith('\"') && val.EndsWith('\"') && val.Length > 0) | |
{ | |
var inner = val.Substring(1, val.Length - 2); | |
return Result<Value, EngineError>.Ok(Value.String(inner)); | |
} | |
return Result<Value, EngineError>.Err(EngineError.MismatchType); | |
} | |
public static Result<Value, EngineError> ParseInt(string val) | |
{ | |
if (long.TryParse(val, out var l)) | |
{ | |
return Result<Value, EngineError>.Ok(Value.Int(l)); | |
} | |
return Result<Value, EngineError>.Err(EngineError.MismatchType); | |
} | |
public static Result<Value, EngineError> ParseValue(string val) | |
{ | |
if (val.StartsWith('\"') && val.EndsWith('\"') && val.Length > 0) | |
{ | |
return ParseString(val); | |
} | |
return ParseInt(val); | |
} | |
public static Result<Command, EngineError> ParseSet(IList<string> input) | |
{ | |
if (input.Count != 3) | |
{ | |
return Result<Command, EngineError>.Err(EngineError.MismatchNumParams); | |
} | |
var varName = ParseVarName(input[1]); | |
var value = ParseValue(input[2]); | |
if (varName.ErrVal != default) | |
{ | |
return Result<Command, EngineError>.Err(varName.ErrVal); | |
} | |
if (value.ErrVal != default) | |
{ | |
return Result<Command, EngineError>.Err(value.ErrVal); | |
} | |
return Result<Command, EngineError>.Ok(Command.SetVar(varName.OkVal!, value.OkVal!)); | |
} | |
public static Result<Command, EngineError> ParseGet(IList<string> input) | |
{ | |
if (input.Count != 2) | |
{ | |
return Result<Command, EngineError>.Err(EngineError.MismatchNumParams); | |
} | |
var varName = ParseVarName(input[1]); | |
if (varName.ErrVal != default) | |
{ | |
return Result<Command, EngineError>.Err(varName.ErrVal); | |
} | |
return Result<Command, EngineError>.Ok(Command.GetVar(varName.OkVal!)); | |
} | |
public static Result<Command, EngineError> ParsePushVar(IList<string> input) | |
{ | |
if (input.Count != 2) | |
{ | |
return Result<Command, EngineError>.Err(EngineError.MismatchNumParams); | |
} | |
var varName = ParseVarName(input[1]); | |
if (varName.ErrVal != default) | |
{ | |
return Result<Command, EngineError>.Err(varName.ErrVal); | |
} | |
return Result<Command, EngineError>.Ok(Command.PushVar(varName.OkVal!)); | |
} | |
public static Result<Command, EngineError> ParsePush(IList<string> input) | |
{ | |
if (input.Count != 2) | |
{ | |
return Result<Command, EngineError>.Err(EngineError.MismatchNumParams); | |
} | |
var val = ParseValue(input[1]); | |
if (val.ErrVal != default) | |
{ | |
return Result<Command, EngineError>.Err(val.ErrVal); | |
} | |
return Result<Command, EngineError>.Ok(Command.Push(val.OkVal!)); | |
} | |
public static Result<IList<Command>, EngineError> Parse(string input) | |
{ | |
var output = new List<Command>(); | |
foreach (var line in input.Split('\n')) | |
{ | |
string[] lineComponents = line.Split(' '); | |
var keyWord = lineComponents.FirstOrDefault(); | |
Result<Command, EngineError>? result; | |
switch (keyWord) | |
{ | |
case "set": | |
result = ParseSet(lineComponents); | |
break; | |
case "get": | |
result = ParseGet(lineComponents); | |
break; | |
case "push": | |
result = ParsePush(lineComponents); | |
break; | |
case "pushvar": | |
result = ParsePushVar(lineComponents); | |
break; | |
case "pop": | |
result = Result<Command, EngineError>.Ok(Command.Pop); | |
break; | |
case "add": | |
result = Result<Command, EngineError>.Ok(Command.Add); | |
break; | |
case null: | |
result = default; | |
break; | |
default: | |
return Result<IList<Command>, EngineError>.Err(EngineError.UnknownCommand(keyWord)); | |
} | |
if (result?.OkVal != default) | |
{ | |
output.Add(result.OkVal); | |
} | |
} | |
return Result<IList<Command>, EngineError>.Ok(output); | |
} | |
} | |
public class Tests | |
{ | |
[Fact] | |
public void Test1() | |
{ | |
var commands = new List<Command> | |
{ | |
Command.SetVar("a", Value.Int(100)), | |
Command.GetVar("a"), | |
}; | |
var evaluator = new Evaluator(); | |
var result = evaluator.Evaluate(commands); | |
Assert.Equal(100, result.OkVal!.IntValue!.Value); | |
} | |
[Fact] | |
public void EvalSetGet() | |
{ | |
const string input = "set x 30\nget x"; | |
var commands = ParseUtils.Parse(input).OkVal!; | |
var evaluator = new Evaluator(); | |
var result = evaluator.Evaluate(commands); | |
Assert.Equal(30, result.OkVal!.IntValue!.Value); | |
} | |
[Fact] | |
public void EvalStack() | |
{ | |
const string input = "push 100\npush 30\nadd\npop"; | |
var commands = ParseUtils.Parse(input).OkVal!; | |
var evaluator = new Evaluator(); | |
var result = evaluator.Evaluate(commands); | |
Assert.Equal(130, result.OkVal!.IntValue!.Value); | |
} | |
[Fact] | |
public void EvalPushvar() | |
{ | |
const string input = "set x 33\npushvar x\npush 100\nadd\npop"; | |
var commands = ParseUtils.Parse(input).OkVal!; | |
var evaluator = new Evaluator(); | |
var result = evaluator.Evaluate(commands); | |
Assert.Equal(133, result.OkVal!.IntValue!.Value); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment