I am hacking around with the Good For Nothing (GFN) compiler, trying to make it do a few different things. I am using the code from here: https://github.com/johandanforth/good-for-nothing-compiler
Regular GFN for loop:
var x = 0;
for x = 0 to 3 do
print x;
end;
This for loop always increments. I'd like to add decrement functionality:
var x = 0;
for x = 3 to 0 down //up for increment (works same as do)
print x;
end;
The main area I am struggling with is the CodeGen.
ForLoop class:
public class ForLoop : Stmt
{
public Stmt Body { get; set; }
public Expr From { get; set; }
public string Ident { get; set; }
public Expr To { get; set; }
public ArithOp Type { get; set; }
}
ArithOp enum:
public enum ArithOp
{
Add,
Sub,
Mul,
Div,
Up,
Down
}
Inside CodeGen.cs:
private void GenStmt(Stmt stmt)
{
//code omitted for brevity
else if (stmt is ForLoop)
{
// example:
// for x = 0 to 100 up
// "hello";
// end;
// x = 0
var forLoop = (ForLoop)stmt;
var assign = new Assign { Ident = forLoop.Ident, Expr = forLoop.From };
GenStmt(assign);
// jump to the test
var test = _il.DefineLabel();
_il.Emit(OpCodes.Br, test);
// statements in the body of the for loop
var body = _il.DefineLabel();
_il.MarkLabel(body);
GenStmt(forLoop.Body);
// to (increment/decrement the value of x)
_il.Emit(OpCodes.Ldloc, SymbolTable[forLoop.Ident]);
_il.Emit(OpCodes.Ldc_I4, 1);
_il.Emit(forLoop.Type == ArithOp.Up ? OpCodes.Add : OpCodes.Sub);
GenerateStoreFromStack(forLoop.Ident, typeof(int));
// **test** does x equal 100? (do the test)
_il.MarkLabel(test);
_il.Emit(OpCodes.Ldloc, SymbolTable[forLoop.Ident]);
GenerateLoadToStackForExpr(forLoop.To, typeof(int));
_il.Emit(OpCodes.Blt, body);
}
}
private void GenerateStoreFromStack(string name, Type type)
{
if (!SymbolTable.ContainsKey(name))
throw new Exception("undeclared variable '" + name + "'");
var locb = SymbolTable[name];
var localType = locb.LocalType;
if (localType != type)
throw new Exception(string.Format("'{0}' is of type {1} but attempted to store value of type {2}", name,
localType == null ? "<unknown>" : localType.Name, type.Name));
_il.Emit(OpCodes.Stloc, SymbolTable[name]);
}
private void GenerateLoadToStackForExpr(Expr expr, Type expectedType)
{
Type deliveredType;
if (expr is StringLiteral)
{
deliveredType = typeof(string);
_il.Emit(OpCodes.Ldstr, ((StringLiteral)expr).Value);
}
else if (expr is IntLiteral)
{
deliveredType = typeof(int);
_il.Emit(OpCodes.Ldc_I4, ((IntLiteral)expr).Value);
}
else if (expr is Variable)
{
var ident = ((Variable)expr).Ident;
deliveredType = expr.GetType();
if (!SymbolTable.ContainsKey(ident))
{
throw new Exception("undeclared variable '" + ident + "'");
}
_il.Emit(OpCodes.Ldloc, SymbolTable[ident]);
}
else if (expr is ArithExpr)
{
var arithExpr = (ArithExpr)expr;
var left = arithExpr.Left;
var right = arithExpr.Right;
deliveredType = expr.GetType();
GenerateLoadToStackForExpr(left, expectedType);
GenerateLoadToStackForExpr(right, expectedType);
switch (arithExpr.Op)
{
case ArithOp.Add:
_il.Emit(OpCodes.Add);
break;
case ArithOp.Sub:
_il.Emit(OpCodes.Sub);
break;
case ArithOp.Mul:
_il.Emit(OpCodes.Mul);
break;
case ArithOp.Div:
_il.Emit(OpCodes.Div);
break;
default:
throw new NotImplementedException("Don't know how to generate il load code for " + arithExpr.Op +
" yet!");
}
}
else
{
throw new Exception("don't know how to generate " + expr.GetType().Name);
}
if (deliveredType == expectedType) return;
if (deliveredType != typeof (int) || expectedType != typeof (string))
throw new Exception("can't coerce a " + deliveredType.Name + " to a " + expectedType.Name);
_il.Emit(OpCodes.Box, typeof (int));
_il.Emit(OpCodes.Callvirt, typeof (object).GetMethod("ToString"));
}
This currently generates an .exe that does nothing. Sources I have looked at to help solve this: http://www.codeproject.com/Articles/3778/Introduction-to-IL-Assembly-Language#Loop and https://ninjaferret.wordpress.com/2009/12/23/msil-4-for-loops/. I just don't know enough IL
Do this in C# code to get insight:
for (int ix = 0; ix < 3; ++ix) // up
for (int ix = 3; ix > 0; --ix) // down
There are two changes, you got the difference in the inc/dec operator. You didn't get the change in the loop termination condition. Which makes this the bug:
_il.Emit(OpCodes.Blt, body);
You'll have to invert that to Opcodes.Bgt
Related
I would like to write C# code that parses nested parenthesis to array elements, but only on first level. An example is needed for sure:
I want this string:
"(example (to (parsing nested paren) but) (first lvl only))"
tp be parsed into:
["example", "(to (parsing nested paren) but)", "(first lvl only)"]
I was thinking about using regex but can't figure out how to properly use them without implementing this behaviour from scratch.
In the case of malformed inputs I would like to return an empty array, or an array ["error"]
I developed a parser for your example. I also checked some other examples which you can see in the code.
using System;
using System.Collections;
using System.Collections.Generic;
public class Program
{
public static void Main()
{
string str = "(example (to (parsing nested paren) but) (first lvl only))"; // => [example , (to (parsing nested paren) but) , (first lvl only)]
//string str = "(first)(second)(third)"; // => [first , second , third]
//string str = "(first(second)third)"; // => [first , (second) , third]
//string str = "(first(second)(third)fourth)"; // => [first , (second) , (third) , fourth]
//string str = "(first((second)(third))fourth)"; // => [first , ((second)(third)) , fourth]
//string str = "just Text"; // => [ERROR]
//string str = "start with Text (first , second)"; // => [ERROR]
//string str = "(first , second) end with text"; // => [ERROR]
//string str = ""; // => [ERROR]
//string str = "("; // => [ERROR]
//string str = "(first()(second)(third))fourth)"; // => [ERROR]
//string str = "(((extra close pareanthese))))"; // => [ERROR]
var res = Parser.parse(str);
showRes(res);
}
static void showRes(ArrayList res)
{
var strings = res.ToArray();
var theString = string.Join(" , ", strings);
Console.WriteLine("[" + theString + "]");
}
}
public class Parser
{
static Dictionary<TokenType, TokenType> getRules()
{
var rules = new Dictionary<TokenType, TokenType>();
rules.Add(TokenType.OPEN_PARENTHESE, TokenType.START | TokenType.OPEN_PARENTHESE | TokenType.CLOSE_PARENTHESE | TokenType.SIMPLE_TEXT);
rules.Add(TokenType.CLOSE_PARENTHESE, TokenType.SIMPLE_TEXT | TokenType.CLOSE_PARENTHESE);
rules.Add(TokenType.SIMPLE_TEXT, TokenType.SIMPLE_TEXT | TokenType.CLOSE_PARENTHESE | TokenType.OPEN_PARENTHESE);
rules.Add(TokenType.END, TokenType.CLOSE_PARENTHESE);
return rules;
}
static bool isValid(Token prev, Token cur)
{
var rules = Parser.getRules();
return rules.ContainsKey(cur.type) && ((prev.type & rules[cur.type]) == prev.type);
}
public static ArrayList parse(string sourceText)
{
ArrayList result = new ArrayList();
int openParenthesesCount = 0;
Lexer lexer = new Lexer(sourceText);
Token prevToken = lexer.getStartToken();
Token currentToken = lexer.readNextToken();
string tmpText = "";
while (currentToken.type != TokenType.END)
{
if (currentToken.type == TokenType.OPEN_PARENTHESE)
{
openParenthesesCount++;
if (openParenthesesCount > 1)
{
tmpText += currentToken.token;
}
}
else if (currentToken.type == TokenType.CLOSE_PARENTHESE)
{
openParenthesesCount--;
if (openParenthesesCount < 0)
{
return Parser.Error();
}
if (openParenthesesCount > 0)
{
tmpText += currentToken.token;
}
}
else if (currentToken.type == TokenType.SIMPLE_TEXT)
{
tmpText += currentToken.token;
}
if (!Parser.isValid(prevToken, currentToken))
{
return Parser.Error();
}
if (openParenthesesCount == 1 && tmpText.Trim() != "")
{
result.Add(tmpText);
tmpText = "";
}
prevToken = currentToken;
currentToken = lexer.readNextToken();
}
if (openParenthesesCount != 0)
{
return Parser.Error();
}
if (!Parser.isValid(prevToken, currentToken))
{
return Parser.Error();
}
if (tmpText.Trim() != "")
{
result.Add(tmpText);
}
return result;
}
static ArrayList Error()
{
var er = new ArrayList();
er.Add("ERROR");
return er;
}
}
class Lexer
{
string _txt;
int _index;
public Lexer(string text)
{
this._index = 0;
this._txt = text;
}
public Token getStartToken()
{
return new Token(-1, TokenType.START, "");
}
public Token readNextToken()
{
if (this._index >= this._txt.Length)
{
return new Token(-1, TokenType.END, "");
}
Token t = null;
string txt = "";
if (this._txt[this._index] == '(')
{
txt = "(";
t = new Token(this._index, TokenType.OPEN_PARENTHESE, txt);
}
else if (this._txt[this._index] == ')')
{
txt = ")";
t = new Token(this._index, TokenType.CLOSE_PARENTHESE, txt);
}
else
{
txt = this._readText();
t = new Token(this._index, TokenType.SIMPLE_TEXT, txt);
}
this._index += txt.Length;
return t;
}
private string _readText()
{
string txt = "";
int i = this._index;
while (i < this._txt.Length && this._txt[i] != '(' && this._txt[i] != ')')
{
txt = txt + this._txt[i];
i++;
}
return txt;
}
}
class Token
{
public int position
{
get;
private set;
}
public TokenType type
{
get;
private set;
}
public string token
{
get;
private set;
}
public Token(int position, TokenType type, string token)
{
this.position = position;
this.type = type;
this.token = token;
}
}
[Flags]
enum TokenType
{
START = 1,
OPEN_PARENTHESE = 2,
SIMPLE_TEXT = 4,
CLOSE_PARENTHESE = 8,
END = 16
}
well, regex will do the job:
var text = #"(example (to (parsing nested paren) but) (first lvl only))";
var pattern = #"\(([\w\s]+) (\([\w\s]+ \([\w\s]+\) [\w\s]+\)) (\([\w\s]+\))\)*";
try
{
Regex r = new Regex(pattern, RegexOptions.IgnoreCase);
Match m = r.Match(text);
string group_1 = m.Groups[1].Value; //example
string group_2 = m.Groups[2].Value; //(to (parsing nested paren) but)
string group_3 = m.Groups[3].Value; //(first lvl only)
return new string[]{group_1,group_2,group_3};
}
catch(Exception ex){
return new string[]{"error"};
}
hopefully this helps, tested here in dotnetfiddle
Edit:
this might get you started into building the right expression according to whatever patterns you are falling into and maybe build a recursive function to parse the rest into the desired output :)
RegEx is not recursive. You either count bracket level, or recurse.
An non-recursive parser loop I tested for the example you show is..
string SplitFirstLevel(string s)
{
List<string> result = new List<string>();
int p = 0, level = 0;
for (int i = 0; i < s.Length; i++)
{
if (s[i] == '(')
{
level++;
if (level == 1) p = i + 1;
if (level == 2)
{
result.Add('"' + s.Substring(p, i - p) + '"');
p = i;
}
}
if (s[i] == ')')
if (--level == 0)
result.Add('"' + s.Substring(p, i - p) + '"');
}
return "[" + String.Join(",", result) + "]";
}
Note: after some more testing, I see your specification is unclear. How to delimit orphaned level 1 terms, that is terms without bracketing ?
For example, my parser translates
(example (to (parsing nested paren) but) (first lvl only))
to:
["example ","(to (parsing nested paren) but) ","(first lvl only)"]
and
(example (to (parsing nested paren)) but (first lvl only))
to:
["example ","(to (parsing nested paren)) but ","(first lvl only)"]
In either case, "example" gets a separate term, while "but" is grouped with the first term. In the first example this is logical, it is in the bracketing, but it may be unwanted behaviour in the second case, where "but" should be separated, like "example", which also has no bracketing (?)
I am trying to validate the command line arguments and print an error message if there is some error.
My problem is that if the number of command line parameters is increased (currently, I only have 3), then my code would turn into spaghetti code. How can I reduce the cyclomatic complexity of the given code?
var isCmdLineWrong = false;
var Arg1 = "Undefined";
var Arg2 = "Undefined";
var Arg3 = "Undefined";
var commandArguments = Environment.GetCommandLineArgs();
if (commandArguments.Contains("-r") && arguments[commandArguments.IndexOf("-r") + 1].StartsWith("-") == false)
Arg1 = commandArguments[commandArguments.IndexOf("-r") + 1];
else
{
isCmdLineWrong = true;
}
if (commandArguments.Contains("-n") && commandArguments[commandArguments.IndexOf("-n") + 1].StartsWith("-") == false)
Arg2 = commandArguments[commandArguments.IndexOf("-n") + 1];
else
{
isCmdLineWrong = true;
}
if (commandArguments.Contains("-p") && commandArguments[commandArguments.IndexOf("-p") + 1].StartsWith("-") == false)
Arg3 = commandArguments[commandArguments.IndexOf("-p") + 1];
else
{
isCmdLineWrong = true;
}
if (isCmdLineWrong) Console.WriteLine("Parameters structure is inconsistent");
I suggest extracting CommandLine class:
public static class CommandLine {
private static String FindValue(string value) {
var commandArguments = Environment.GetCommandLineArgs();
int index = commandArguments.IndexOf(value);
if (index < 0)
return null;
else if (index >= commandArguments.Length - 1)
return null; // cmd like "myRoutine.exe -p"
else
return commandArguments[index + 1];
}
static CommandLine() {
Arg1 = FindValue("-r");
Arg2 = FindValue("-n");
Arg3 = FindValue("-p");
}
public static String Arg1 { get; private set; }
public static String Arg2 { get; private set; }
public static String Arg3 { get; private set; }
public static bool IsValid {
get {
return Arg1 != null && Arg2 != null && Arg3 != null;
}
}
}
Having this class written you can put
if (!CommandLine.IsValid) {
Console.WriteLine("Parameters structure is inconsistent");
return;
}
if (CommandLine.Arg1 == "quit") {
...
}
This question is a simple example of how to reuse code.
Look for code which appears to have been copied/pasted,
Put it in a function,
Any differences between copies, pass them in as parameters,
Replace the copies with function calls.
The result is
// Returns this option's value from args, or null on error
public string OptionValue(string[] args, string option)
{
try
{
if (args.Contains(option))
{
string value = args[args.IndexOf(option) + 1]; // reuse expressions as well
if (!value.StartsWith("-"))
return value;
}
return null; // null meaning "undefined"
}
catch
{
return null;
}
}
// And now your code
string[] args = Environment.GetCommandLineArgs();
string Arg1 = OptionValue(args, "-r");
string Arg2 = OptionValue(args, "-n");
string Arg3 = OptionValue(args, "-p");
bool isCmdLineWrong = (Arg1 == null ||
Arg2 == null ||
Arg3 == null);
Of course, all this rewriting could have been avoided if you didn't copy/paste code to start with.
Probably the most important thing to observe in your code is that you are doing the exact same thing several times, though with different inputs "-r" and Arg1, "-n" and Arg2, "-p" and Arg3. That is, you have the following code fragment appear three times (minus my reformatting):
if (commandArguments.Contains(…) &&
arguments[commandArguments.IndexOf(…) + 1].StartsWith("-") == false)
{
… = commandArguments[commandArguments.IndexOf(…) + 1];
}
else
{
isCmdLineWrong = true;
}
The Don't Repeat Yourself (DRY) principle tries to warn us from writing copy-and-paste-style repetitious code, and your original code is a pretty clear violation of it.
I suggest that you extract the common code and put it in a separate method. For example:
static bool TryGetArg(string commandArguments, string name, out string value)
{
// Debug.Assert(name.StartsWith("-"));
if (commandArguments.Contains("-") &&
arguments[commandArguments.IndexOf(name) + 1].StartsWith("-") == false)
{
value = commandArguments[commandArguments.IndexOf(name) + 1];
return true;
}
else
{
value = null;
return false;
}
}
Now you replace your repeated if else with the following:
string commandArguments = Environment.GetCommandLineArgs();
string arg1 = null;
string arg2 = null;
string arg3 = null;
bool isCmdLineOk = TryGetArg(commandArguments, "-r", out arg1) &&
TryGetArg(commandArguments, "-n", out arg2) &&
TryGetArg(commandArguments, "-p", out arg3);
if (isCmdLineOk)
{
// do something with `arg1`, `arg2`, `arg3`.
}
else
{
// not all of `arg1`, `arg2`, `arg3` could be set to a value.
Console.WriteLine("Parameters structure is inconsistent");
}
I have an XML like below :
<Decide Name="MemoryCheck" CommonUnit="MB">
<Decision CellColor="Red" Status="Critical" Exp="<=100" />
<Decision CellColor="Yellow" Status="Warning" Exp="<=200 & >100"/>
<Decision CellColor="Green" Status="OK" Exp=">200" />
</Decide>
For Input 50 MB, Output returned should be "Critical-Red"
For Input 142 MB, Output returned should be "Warning-Yellow"
For Input 212 MB, Output returned should be"OK-Green"
How to go about this using C# ??
Xml Name is "Decide.xml" and Code I have now :
XmlDocument xmldecide = new XmlDocument();
xmldecide.Load("C:\\Decide.xml");
XmlNodeList decidelist = xmldecide.GetElementsbyTagName("Decide");
XmlNode xdecide = decidelist[0];
string input = "50"; // Unit in MB
// Now I have to display the desired O/P "Critical-Red"
string input = "142"; // Unit in MB
// Now I have to display the desired O/P "Warning-Yellow"
string input = "212"; // Unit in MB
// Now I have to display the desired O/P "OK-Green"
Just a suggestion - If you have control of that xml you should consider creating a min and max attribute. Having to parse out conditional and integer information from a single attribute is ugly. That said, assuming you can't change the xml, here's a solution. It assumes the conditionals in the attribute are always in a similar format.
public static string AlertLevel(this XDocument decisionDocument, int size)
{
var queryResult = decisionDocument.Descendants("Decision");
foreach (var item in queryResult)
{
var expAttribute = item.Attribute("Exp");
if (expAttribute == null) continue;
var returnString = CreateResultString(item);
int minValue;
int maxValue;
if (expAttribute.Value.Contains(">") && expAttribute.Value.Contains("<="))
{
//evaluate minValue < size > maxValue
var stringValue = expAttribute.Value.Replace("<=", string.Empty).Replace(">", string.Empty).Trim();
var stringValueArray = stringValue.Split('&');
if (int.TryParse(stringValueArray[1], out minValue) &&
int.TryParse(stringValueArray[0], out maxValue))
{
if (minValue < size &&
size < maxValue)
return returnString;
}
}
else if (expAttribute.Value.Contains(">"))
{
//evaluate size > value
var stringValue = expAttribute.Value.Replace(">", string.Empty).Trim();
if (int.TryParse(stringValue, out maxValue))
{
if (size > maxValue)
return returnString;
}
}
else if (expAttribute.Value.Contains("<="))
{
//else evaluate size < value
var stringValue = expAttribute.Value.Replace("<=", string.Empty).Trim();
if (int.TryParse(stringValue, out minValue))
{
if (size < minValue)
return returnString;
}
}
}
return "No condition was met!";
}
private static string CreateResultString(XElement item)
{
var statusAttribute = item.Attribute("Status");
var returnString = statusAttribute == null ? "Status" : statusAttribute.Value;
var colorAttribute = item.Attribute("CellColor");
returnString += colorAttribute == null ? "-Color" : "-" + colorAttribute.Value;
return returnString;
}
usage
var xmlDecide = XDocument.Load("Decide.xml");
Console.WriteLine("50MB: " + xmlDecide.AlertLevel(50));
Console.WriteLine("142MB: " + xmlDecide.AlertLevel(142));
Console.WriteLine("212MB: " + xmlDecide.AlertLevel(212));
EDIT: You can use the same code for use with XmlDocument instead of XDocument. Just change "Attribute" to "Attributes.GetNamedItem" and "Descendants" to "GetElementsByTagName"
This is complicated.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Xml;
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
string XML =
"<Decide Name=\"MemoryCheck\" CommonUnit=\"MB\">" +
"<Decision CellColor=\"Red\" Status=\"Critical\" Exp=\"<=100\" />" +
"<Decision CellColor=\"Yellow\" Status=\"Warning\" Exp=\"<=200 & >100\"/>" +
"<Decision CellColor=\"Green\" Status=\"OK\" Exp=\">200\" />" +
"</Decide>";
XmlDocument doc = new XmlDocument();
doc.LoadXml(XML);
XmlNodeList memoryCheck = doc.GetElementsByTagName("Decision");
foreach(XmlNode decision in memoryCheck)
{
Decision newDecision = new Decision();
Decision.decisions.Add(newDecision);
newDecision.Cellcolor = decision.Attributes.GetNamedItem("CellColor").Value;
newDecision.status = decision.Attributes.GetNamedItem("Status").Value;
newDecision.low = 0;
newDecision.high = null;
string exps = decision.Attributes.GetNamedItem("Exp").Value;
string[] expsArray = exps.Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
foreach (string exp in expsArray)
{
if(exp.StartsWith("<="))
{
newDecision.high = int.Parse(exp.Substring(exp.IndexOf("=") + 1));
}
if(exp.StartsWith(">"))
{
newDecision.low = int.Parse(exp.Substring(exp.IndexOf(">") + 1));
}
}
}
Decision result = Decision.GetBySize(212);
}
}
public class Decision
{
public static List<Decision> decisions = new List<Decision>();
public string Cellcolor { get; set; }
public string status { get; set; }
public int? low { get; set; }
public int? high {get; set;}
public static Decision GetBySize(int memory)
{
Decision newDecision = null;
foreach(Decision decision in decisions)
{
if (memory >= decision.low)
{
if (decision.high == null)
{
newDecision = decision;
break;
}
else
{
if (memory <= decision.high)
{
newDecision = decision;
break;
}
}
}
}
return newDecision;
}
}
}
Is it possible to implement printing out expression code and value given an expression code as string?
class MyClass
{
// Expected output
// 2 + 2 = 4
// v = 3
// Field = 5
// OtherClass.GetInfo() = Hello
// new OtherClass().OtherField = 22
//
void MyMethod()
{
Console.WriteLine(ExpressionPrintString("2 + 2"));
int v = 3;
Console.WriteLine(ExpressionPrintString("v"));
Console.WriteLine(ExpressionPrintString("Field"));
Console.WriteLine(ExpressionPrintString("OtherClass.GetInfo()"));
Console.WriteLine(ExpressionPrintString("new OtherClass().OtherField"));
}
int Field = 5;
}
class OtherClass
{
public static sting GetInfo() { return "Hello"; }
public int OtherField = 22;
}
I am aware of that Expression<TDelegate> allows to do it for Lambda expressions, like this:
// Usage: ExpressionPrintString(() => 2 + 2) // returns "2 + 2 = 4"
static string ExpressionPrintString(Expression<Func<object>> expr, string delimiter = " = ")
{
return expr.ToString().Replace("() => ","")
+ delimiter
+ expr.Compile().DynamicInvoke();
}
but its output is sometimes not pretty, because it returns the internal representation of the expression. For example
protected override void OnPreRenderComplete(EventArgs e)
{
// In this context
// The following call:
string s = ExpressionPrintString(() => Context.Items["UrlRewrite:OriginalUrl"]);
// returned:
// value(ASP.default_aspx).Context.Items.get_Item("UrlRewrite:OriginalUrl") = http://localhost:8011/Companies
// The expected output would be
// Context.Items["UrlRewrite:OriginalUrl"] = http://localhost:8011/Companies
}
I have a class that parses an XML document in C# using XElement.
I parse the XML for example:
IEnumerable<Element> elements =
from topLevelElement in XElement.Parse(xml).Elements("topLevel")
select new Element()
{
LongElement = Int64.Parse(topLevelElement.Element("long").Value),
StringElement = topLevelElement.Element("string").Value,
DateTimeElement = DateTime.Parse(topLevelElement.Element("datetime").Value)
};
What would be the best way to assert that the elements were properly parsed? I would like to check if LongElement, StringElement, and DateTimeElement is not null after parsing, but if there is a better way to go about this, I am open to it.
If you are unsure of the values that may be returned by the elements, you should really be using TryParse e.g.
int i = 0;
string s = "3";
if (Int32.TryParse(s, out i))
{
// Valid integer, now stored in i.
}
else
{
// Invalid integer.
}
Both your data types DateTime and Int32 have TryParse as an available method. As for a string, you can just do a trivial == null or String.IsNullOrEmpty
I would use functions from within Linq. These allow you to either throw an exception or set required defaults if you want your application to be not so strict ;)
Anyways, you get more control:
var elements = from topLevelElement in XElement.Parse(xml).Elements("topLevel")
select new Element()
{
LongElement = ConvertToInt(topLevelElement.Element("long").Value),
StringElement = topLevelElement.Element("string").Value,
DateTimeElement = DateTime.Parse(topLevelElement.Element("datetime").Value)
};
Where within ConvertToInt could do all you want, like:
public int ConvertToInt(object value)
{
if(value is int)
// return converted value
else
// return default, throw exception, etc
}
This is also a more reusable layout.
I would store the parse states in the element as a KeyValuePair:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
namespace ConsoleApplication1
{
internal class Program
{
private static void Main(string[] args)
{
var states = new string[] { "null", "empty", "noparse", "value" };
var xml = "<root>";
xml += "<topLevel><long>-13451245234</long><string>hello world</string><datetime>1/1/2012 8:00AM</datetime></topLevel>";
xml += "<topLevel><long>4563264643</long><string>lipsum</string><datetime></datetime></topLevel>";
xml += "<topLevel><string>hello world</string><datetime>1/1/2012 8:00AM</datetime></topLevel>";
xml += "</root>";
IEnumerable<Element> elements =
from topLevelElement in XElement.Parse(xml).Elements("topLevel")
select new Element
{
LongElement = ParseValue(topLevelElement, "long"),
DateTimeElement = ParseValue(topLevelElement, "datetime"),
StringElement = ParseValue(topLevelElement, "string"),
};
var idx = 0;
elements.All(e =>
{
Console.WriteLine("---- ELEMENT #{0} -----",idx++);
Console.WriteLine("[long] State: {0}\tValue:{1}\tType:{2}", states[e.LongElement.Key], e.LongElement.Value, (e.LongElement.Value).GetType());
Console.WriteLine("[datetime] State: {0}\tValue:{1}\tType:{2}", states[e.DateTimeElement.Key], e.DateTimeElement.Value, (e.DateTimeElement.Value).GetType());
Console.WriteLine("[string] State: {0}\tValue:{1}\tType:{2}", states[e.StringElement.Key], e.StringElement.Value, (e.StringElement.Value).GetType());
return true;
});
}
private static dynamic ParseValue(XElement parent, String propname)
{
var prop = parent.Element(propname);
dynamic val = null;
byte state = 255;
if (prop == null) state = 0;
else if (string.IsNullOrEmpty(prop.Value)) state = 1;
if (state < 255) return GetKVP(propname, state, GetDefaultValue(propname));
switch (propname)
{
case "string":
state = 3;
val = prop.Value;
break;
case "long":
Int64 longvalue;
if (Int64.TryParse(prop.Value, out longvalue)) { state = 3; val = longvalue; }
else state = 2;
break;
case "datetime":
DateTime datetimevalue;
if (DateTime.TryParse(prop.Value, out datetimevalue)) { state = 3; val = datetimevalue; }
else state = 2;
break;
default:
val = GetDefaultValue(propname);
break;
}
return GetKVP(propname,state,val);
}
private static dynamic GetKVP(string propname, byte state, object val)
{
if (propname == "long") return new KeyValuePair<byte, Int64>(state, (Int64)val);
if (propname == "datetime") return new KeyValuePair<byte, DateTime>(state, (DateTime)val);
if (propname == "string") return new KeyValuePair<byte, String>(state, (String)val);
return null;
}
private static dynamic GetDefaultValue(string propname)
{
if (propname == "long") return long.MinValue;
if (propname == "datetime") return DateTime.MinValue;
if (propname == "string") return null;
return null;
}
#region Nested type: Element
public struct Element
{
// States stored as byte, 0 = null, 1= empty, 2 = has a value
public KeyValuePair<byte,Int64> LongElement { get; set; }
public KeyValuePair<byte,String> StringElement { get; set; }
public KeyValuePair<byte,DateTime> DateTimeElement { get; set; }
}
#endregion
}
}