using System;
using System.Collections.Generic;
using System.Linq;
namespace Semmle.Extraction
{
///
/// An ID. Either a fresh ID (`*`), a key, or a label (https://semmle.com/wiki/display/IN/TRAP+Files):
///
/// ```
/// id ::= '*' | key | label
/// ```
///
public interface IId
{
///
/// Appends this ID to the supplied trap builder.
///
void AppendTo(ITrapBuilder tb);
}
///
/// A fresh ID (`*`).
///
public class FreshId : IId
{
FreshId() { }
///
/// Gets the singleton instance.
///
public static IId Instance { get; } = new FreshId();
public override string ToString() => "*";
public override bool Equals(object obj) => obj.GetType() == GetType();
public override int GetHashCode() => 0;
public void AppendTo(ITrapBuilder tb)
{
tb.Append("*");
}
}
///
/// A key. Either a simple key, e.g. `@"bool A.M();method"`, or a compound key, e.g.
/// `@"{0} {1}.M();method"` where `0` and `1` are both labels.
///
public class Key : IId
{
readonly IdTrapBuilder TrapBuilder;
///
/// Creates a new key by concatenating the contents of the supplied
/// arguments.
///
public Key(params object[] args)
{
TrapBuilder = new IdTrapBuilder();
foreach (var arg in args)
TrapBuilder.Append(arg);
}
///
/// Creates a new key by applying the supplied action to an empty
/// trap builder.
///
public Key(Action action)
{
TrapBuilder = new IdTrapBuilder();
action(TrapBuilder);
}
public override string ToString()
{
// Only implemented for debugging purposes
var tsb = new TrapStringBuilder();
AppendTo(tsb);
return tsb.ToString();
}
public override bool Equals(object obj)
{
if (obj.GetType() != GetType())
return false;
var id = (Key)obj;
return id.TrapBuilder.Fragments.SequenceEqual(TrapBuilder.Fragments);
}
public override int GetHashCode()
{
unchecked
{
int hash = 17;
foreach (var fragment in TrapBuilder.Fragments)
{
hash = hash * 23 + fragment.GetHashCode();
}
return hash;
}
}
public void AppendTo(ITrapBuilder tb)
{
tb.Append("@\"");
foreach (var fragment in TrapBuilder.Fragments)
tb.Append(fragment);
tb.Append("\"");
}
class IdTrapBuilder : ITrapBuilder
{
readonly public List Fragments = new List();
public ITrapBuilder Append(object arg)
{
if (arg is IEntity)
{
var key = ((IEntity)arg).Label;
Fragments.Add("{#");
Fragments.Add(key.Value.ToString());
Fragments.Add("}");
}
else
Fragments.Add(arg.ToString());
return this;
}
public ITrapBuilder Append(string arg)
{
Fragments.Add(arg);
return this;
}
public ITrapBuilder AppendLine()
{
throw new NotImplementedException();
}
}
}
///
/// A label referencing an entity, of the form "#123".
///
public struct Label : IId
{
public Label(int value) : this()
{
Value = value;
}
public int Value { get; private set; }
static public readonly Label InvalidLabel = new Label(0);
public bool Valid => Value > 0;
public override string ToString()
{
if (!Valid)
throw new NullReferenceException("Attempt to use an invalid label");
return "#" + Value;
}
public static bool operator ==(Label l1, Label l2) => l1.Value == l2.Value;
public static bool operator !=(Label l1, Label l2) => l1.Value != l2.Value;
public override bool Equals(object other)
{
return GetType() == other.GetType() && ((Label)other).Value == Value;
}
public override int GetHashCode() => 61 * Value;
///
/// Constructs a unique string for this label.
///
/// The trap builder used to store the result.
public void AppendTo(ITrapBuilder tb)
{
if (!Valid)
throw new NullReferenceException("Attempt to use an invalid label");
tb.Append("#").Append(Value);
}
}
}