using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using Iot.Device.Arduino;
using Iot.Device.Board;
using Iot.Device.Common;
using Microsoft.Extensions.Logging;
namespace ArduinoCsCompiler
{
public class WriteRuntimeCoreData
{
private const string AutoGeneratedMessage = "// This code is autogenerated. Any change will be lost when 'acs prepare' is run";
private readonly ILogger _logger;
private string _targetPath;
private string _targetRootPath;
public WriteRuntimeCoreData()
: this(null)
{
}
public WriteRuntimeCoreData(string? toPath)
{
_logger = this.GetCurrentClassLogger();
if (toPath == null)
{
_targetRootPath = GetRuntimePath();
}
else
{
_targetRootPath = toPath;
}
_targetPath = Path.Combine(_targetRootPath, "interface");
}
public string TargetPath => _targetPath;
public string TargetRootPath => _targetRootPath;
public void Write()
{
if (!Directory.Exists(_targetRootPath))
{
_logger.LogWarning($"Warning: {_targetRootPath} does not exist. Please ensure it is correct and make sure the runtime is checked out correctly");
}
Directory.CreateDirectory(_targetPath);
WriteBreakpointTypes();
WriteNativeMethodDefinitions();
WriteDebuggerCommands();
WriteExceptionClauseTypes();
WriteKnownTypeTokens();
WriteMethodFlags();
WritePinUsage();
WriteRuntimeState();
WriteSystemExceptions();
WriteExecutorCommands();
WriteVariableKind();
}
private string GetRuntimePath()
{
string path = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);
return Path.Combine(path, @"Arduino\ExtendedConfigurableFirmata");
}
private void WriteNativeMethodDefinitions()
{
Assembly[] typesWhereToLook = new Assembly[]
{
Assembly.GetAssembly(typeof(MicroCompiler))!,
Assembly.GetAssembly(typeof(ArduinoBoard))!
};
string[] specials = new string[]
{
"ByReferenceCtor",
"ByReferenceValue",
};
Dictionary<string, int> entries = new();
foreach (var s in specials)
{
entries.Add(s, ArduinoImplementationAttribute.GetStaticHashCode(s));
}
foreach (var a in typesWhereToLook)
{
foreach (var type in a.GetTypes())
{
foreach (var method in type.GetMethods(BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public))
{
var attribs = method.GetCustomAttributes(typeof(ArduinoImplementationAttribute)).Cast<ArduinoImplementationAttribute>();
var attrib = attribs.FirstOrDefault();
if (attrib != null && attrib.MethodNumber != 0)
{
TryAddEntry(entries, attrib);
}
}
foreach (var method in type.GetConstructors(BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public))
{
var attribs = method.GetCustomAttributes(typeof(ArduinoImplementationAttribute)).Cast<ArduinoImplementationAttribute>();
var attrib = attribs.FirstOrDefault();
if (attrib != null && attrib.MethodNumber != 0)
{
TryAddEntry(entries, attrib);
}
}
}
}
var duplicates = entries.GroupBy(x => x.Value)
.Where(g => g.Count() > 1).ToList();
if (duplicates.Any())
{
var dup = duplicates.First();
var s = entries.First(x => x.Value == dup.Key);
throw new InvalidOperationException($"Duplicate method keys found: {dup.Key}: {s}");
}
var list = entries.OrderBy(x => x.Key).Select(y => (y.Key, y.Value));
var reverseLookupList = entries.OrderBy(x => x.Value).Select(y => (y.Value, y.Key));
WriteNativeMethodList(list, reverseLookupList);
}
private void TryAddEntry(Dictionary<string, int> entries, ArduinoImplementationAttribute attrib)
{
if (!entries.ContainsKey(attrib.Name))
{
entries.Add(attrib.Name, attrib.MethodNumber);
}
else if (entries[attrib.Name] == attrib.MethodNumber)
{
}
else
{
throw new InvalidOperationException($"Method {attrib.Name} was already declared with a different hash code");
}
}
private void WriteNativeMethodList(IEnumerable<(string Key, int Value)> entries, IEnumerable<(int Value, string Key)> reverseLookupList)
{
string name = "NativeMethod";
string header = FormattableString.Invariant($@"
#pragma once
{AutoGeneratedMessage}
// Native method numbers, ordered by method name
enum class {name}
{{
None = 0,
");
string outputFile = Path.Combine(_targetPath, name + ".h");
TextWriter w = new StreamWriter(outputFile, false, Encoding.ASCII);
w.Write(header);
foreach (var e in entries)
{
w.WriteLine(FormattableString.Invariant($" {e.Key} = {e.Value},"));
}
w.WriteLine("};");
w.WriteLine("/* Reverse lookup list (ordered by value)");
foreach (var e in reverseLookupList)
{
w.WriteLine($"{e.Value} (0x{e.Value:X}) -> {e.Key}");
}
w.WriteLine("*/");
w.Close();
}
private void WriteSystemExceptions()
{
WriteEnumHeaderFile<SystemException>();
}
private void WriteMethodFlags()
{
WriteEnumHeaderFile<MethodFlags>();
}
private void WriteKnownTypeTokens()
{
WriteEnumHeaderFile<KnownTypeTokens>();
}
private void WriteExceptionClauseTypes()
{
WriteEnumHeaderFile<ExceptionHandlingClauseOptions>();
}
private void WriteRuntimeState()
{
WriteEnumHeaderFile<RuntimeState>();
}
private void WriteDebuggerCommands()
{
WriteEnumHeaderFile<DebuggerCommand>();
}
private void WriteBreakpointTypes()
{
WriteEnumHeaderFile<BreakpointType>();
}
private void WritePinUsage()
{
WriteEnumHeaderFile<PinUsage>();
}
private void WriteExecutorCommands()
{
string name = nameof(ExecutorCommand);
string header = FormattableString.Invariant($@"
#pragma once
{AutoGeneratedMessage}
enum class {name} : byte
{{
");
string outputFile = Path.Combine(_targetPath, name + ".h");
TextWriter w = new StreamWriter(outputFile, false, Encoding.ASCII);
w.Write(header);
foreach (var e in Enum.GetValues(typeof(ExecutorCommand)))
{
w.WriteLine(FormattableString.Invariant($" {e.ToString()} = {(byte)e},"));
}
w.WriteLine("};");
w.Close();
}
private void WriteVariableKind()
{
WriteEnumHeaderFile<VariableKind>();
}
private void WriteEnumHeaderFile<T>()
where T : struct, Enum
{
string name = typeof(T).Name;
string size = string.Empty;
if (Enum.GetUnderlyingType(typeof(T)) == typeof(byte))
{
size = " : byte";
}
string header = FormattableString.Invariant($@"
#pragma once
{AutoGeneratedMessage}
enum class {name}{size}
{{
");
string outputFile = Path.Combine(_targetPath, name + ".h");
TextWriter w = new StreamWriter(outputFile, false, Encoding.ASCII);
w.Write(header);
foreach (var e in Enum.GetValues<T>())
{
w.WriteLine(FormattableString.Invariant($" {e.ToString()} = {GetIntValueFromEnum(e)},"));
}
w.WriteLine("};");
w.Close();
}
private int GetIntValueFromEnum<T>(T value)
where T : Enum
{
return Convert.ToInt32(value);
}
}
}