diff --git a/src/FirmwareGenerator.cs b/src/FirmwareGenerator.cs
index c9b7634..17de070 100644
--- a/src/FirmwareGenerator.cs
+++ b/src/FirmwareGenerator.cs
@@ -1,6 +1,6 @@
-using System.Collections;
+using System.CodeDom.Compiler;
+using System.Collections;
using System.Collections.Generic;
-using System.IO;
namespace Harp.Generators;
@@ -16,6 +16,7 @@ public class FirmwareGenerator
readonly AppRegs _appRegsTemplate = new();
readonly AppRegsImpl _appRegsImplTemplate = new();
readonly Interrupts _interruptsTemplate = new();
+ readonly CompilerErrorCollection errors = [];
///
/// Initializes a new instance of the class with the
@@ -30,22 +31,20 @@ public FirmwareGenerator(DeviceInfo deviceMetadata, Dictionary
+ /// Gets the collection of errors emitted during the code generation process.
+ ///
+ public CompilerErrorCollection Errors => errors;
+
///
/// Generates firmware header files complying with the specified metadata file.
///
diff --git a/src/Interface.cs b/src/Interface.cs
index 5fa2365..b000b0c 100644
--- a/src/Interface.cs
+++ b/src/Interface.cs
@@ -468,7 +468,7 @@ public static string GetDefaultValueAssignment(float? defaultValue, float? minVa
{
defaultValue ??= minValue;
var suffix = payloadType == PayloadType.Float ? "F" : string.Empty;
- return defaultValue.HasValue? $" = {defaultValue}{suffix};" : string.Empty;
+ return defaultValue.HasValue ? $" = {defaultValue}{suffix};" : string.Empty;
}
public static string GetParseConversion(RegisterInfo register, string expression)
@@ -655,7 +655,7 @@ public static string GetPayloadMemberValueFormatter(
{
if (member.HasConverter)
expression = $"FormatPayload{name}({expression})";
-
+
if (member.Length > 0)
return expression;
@@ -835,7 +835,7 @@ public void WriteYaml(IEmitter emitter, object value, Type type, ObjectSerialize
emitter.Emit(new Scalar(access.ToString()));
return;
}
-
+
emitter.Emit(new SequenceStart(AnchorName.Empty, TagName.Empty, isImplicit: true, SequenceStyle.Flow));
if ((access | RegisterAccess.Read) != 0)
emitter.Emit(new Scalar(nameof(RegisterAccess.Read)));
diff --git a/src/InterfaceGenerator.cs b/src/InterfaceGenerator.cs
index 1b5aded..c847b15 100644
--- a/src/InterfaceGenerator.cs
+++ b/src/InterfaceGenerator.cs
@@ -1,6 +1,6 @@
-using System.Collections;
+using System.CodeDom.Compiler;
+using System.Collections;
using System.Collections.Generic;
-using System.IO;
namespace Harp.Generators;
@@ -11,6 +11,7 @@ public sealed class InterfaceGenerator
{
readonly Device _deviceTemplate = new();
readonly AsyncDevice _asyncDeviceTemplate = new();
+ readonly CompilerErrorCollection errors = [];
///
/// Initializes a new instance of the class with the
@@ -25,12 +26,15 @@ public InterfaceGenerator(DeviceInfo deviceMetadata, string ns)
{ "Namespace", ns },
{ "DeviceMetadata", deviceMetadata }
};
- _deviceTemplate.Session = session;
- _asyncDeviceTemplate.Session = session;
- _deviceTemplate.Initialize();
- _asyncDeviceTemplate.Initialize();
+ _deviceTemplate.Initialize(InterfaceImplementation.DeviceFileName, errors, session);
+ _asyncDeviceTemplate.Initialize(InterfaceImplementation.AsyncDeviceFileName, errors, session);
}
+ ///
+ /// Gets the collection of errors emitted during the code generation process.
+ ///
+ public CompilerErrorCollection Errors => errors;
+
///
/// Generates a device interface implementation complying with the specified metadata file.
///
diff --git a/src/TemplateBase.cs b/src/TemplateBase.cs
index 55e2640..61f4fdd 100644
--- a/src/TemplateBase.cs
+++ b/src/TemplateBase.cs
@@ -7,100 +7,114 @@ namespace Harp.Generators;
internal abstract class TemplateBase
{
- private StringBuilder builder;
+ private StringBuilder builder;
private CompilerErrorCollection errors;
private string currentIndent = string.Empty;
private Stack indents;
-
+
public virtual IDictionary Session { get; set; }
-
+
+ public string FileName { get; set; } = string.Empty;
+
public StringBuilder GenerationEnvironment
{
get => builder ??= new StringBuilder();
set => builder = value;
}
-
- protected CompilerErrorCollection Errors => errors ??= [];
-
+
+ public CompilerErrorCollection Errors
+ {
+ get => errors ??= [];
+ set => errors = value;
+ }
+
public string CurrentIndent => currentIndent;
-
+
private Stack Indents => indents ??= [];
-
+
public ToStringInstanceHelper ToStringHelper { get; } = new();
public abstract string TransformText();
public virtual void Initialize() { }
-
+
+ public void Initialize(string fileName, CompilerErrorCollection errors, Dictionary session)
+ {
+ FileName = fileName;
+ Errors = errors;
+ Session = session;
+ Initialize();
+ }
+
public void Error(string message)
{
- Errors.Add(new CompilerError(null, -1, -1, null, message));
+ Errors.Add(new CompilerError(FileName, -1, -1, null, message));
}
-
+
public void Warning(string message)
{
- Errors.Add(new CompilerError(null, -1, -1, null, message)
+ Errors.Add(new CompilerError(FileName, -1, -1, null, message)
{
IsWarning = true
});
}
-
+
public string PopIndent()
{
if (Indents.Count == 0)
return string.Empty;
-
+
int lastPos = currentIndent.Length - Indents.Pop();
string last = currentIndent.Substring(lastPos);
currentIndent = currentIndent.Substring(0, lastPos);
return last;
}
-
+
public void PushIndent(string indent)
{
Indents.Push(indent.Length);
currentIndent += indent;
}
-
+
public void ClearIndent()
{
currentIndent = string.Empty;
Indents.Clear();
}
-
+
public void Write(string textToAppend)
{
GenerationEnvironment.Append(textToAppend);
}
-
+
public void Write(string format, params object[] args)
{
GenerationEnvironment.AppendFormat(format, args);
}
-
+
public void WriteLine(string textToAppend)
{
GenerationEnvironment.Append(currentIndent);
GenerationEnvironment.AppendLine(textToAppend);
}
-
+
public void WriteLine(string format, params object[] args)
{
GenerationEnvironment.Append(currentIndent);
GenerationEnvironment.AppendFormat(format, args);
GenerationEnvironment.AppendLine();
}
-
+
public class ToStringInstanceHelper
- {
+ {
private IFormatProvider formatProvider = System.Globalization.CultureInfo.InvariantCulture;
-
+
public IFormatProvider FormatProvider
{
get => formatProvider;
set => formatProvider = value ?? formatProvider;
}
-
+
public string ToStringWithCulture(object value)
{
if (value is null)
@@ -108,11 +122,11 @@ public string ToStringWithCulture(object value)
if (value is IConvertible convertible)
return convertible.ToString(formatProvider);
-
+
var method = value.GetType().GetMethod(nameof(ToString), [typeof(IFormatProvider)]);
if (method is not null)
return (string)method.Invoke(value, [formatProvider]);
-
+
return value.ToString();
}
}
diff --git a/tests/FirmwareGeneratorTests.cs b/tests/FirmwareGeneratorTests.cs
index e4eb242..7de67c3 100644
--- a/tests/FirmwareGeneratorTests.cs
+++ b/tests/FirmwareGeneratorTests.cs
@@ -17,7 +17,8 @@ public void Initialize()
[DataTestMethod]
[DataRow("device.yml")]
- public void FirmwareTemplate_GenerateAndBuildWithoutErrors(string metadataFileName)
+ [DataRow("errors.yml", "Interrupt number must be specified if interrupt priority is set.")]
+ public void FirmwareTemplate_GenerateAndBuild(string metadataFileName, params string[] expectedErrors)
{
metadataFileName = TestHelper.GetMetadataPath(metadataFileName);
var iosMetadataFileName = Path.ChangeExtension(metadataFileName, ".ios.yml");
@@ -37,6 +38,7 @@ public void FirmwareTemplate_GenerateAndBuildWithoutErrors(string metadataFileNa
var interruptsOutputFileName = $"{outputFileName}.{FirmwareImplementation.InterruptsFileName}";
try
{
+ TestHelper.AssertExpectedGeneratorErrors(generator.Errors, expectedErrors);
TestHelper.AssertExpectedOutput(headers.App, appOutputFileName);
TestHelper.AssertExpectedOutput(implementation.App, appImplOutputFileName);
TestHelper.AssertExpectedOutput(headers.AppFuncs, appFuncsOutputFileName);
diff --git a/tests/InterfaceGeneratorTests.cs b/tests/InterfaceGeneratorTests.cs
index 3840fa5..a9eda41 100644
--- a/tests/InterfaceGeneratorTests.cs
+++ b/tests/InterfaceGeneratorTests.cs
@@ -32,6 +32,7 @@ public void DeviceTemplate_GenerateAndBuildWithoutErrors(string metadataFileName
var customImplementation = TestHelper.GetManifestResourceText($"EmbeddedSources.{outputFileName}.cs");
try
{
+ TestHelper.AssertNoGeneratorErrors(generator.Errors);
CompilerTestHelper.CompileFromSource(implementation.Device, implementation.AsyncDevice, payloadExtensions, customImplementation);
TestHelper.AssertExpectedOutput(implementation.Device, deviceOutputFileName);
TestHelper.AssertExpectedOutput(implementation.AsyncDevice, asyncDeviceOutputFileName);
diff --git a/tests/Metadata/errors.ios.yml b/tests/Metadata/errors.ios.yml
new file mode 100644
index 0000000..32620d0
--- /dev/null
+++ b/tests/Metadata/errors.ios.yml
@@ -0,0 +1,8 @@
+# yaml-language-server: $schema=https://harp-tech.org/draft-02/schema/ios.json
+IO_PULLDOWN:
+ port: PORTJ
+ pinNumber: 0
+ direction: input
+ pinMode: pulldown
+ triggerMode: toggle
+ interruptPriority: low
\ No newline at end of file
diff --git a/tests/Metadata/errors.yml b/tests/Metadata/errors.yml
new file mode 100644
index 0000000..c9c5a40
--- /dev/null
+++ b/tests/Metadata/errors.yml
@@ -0,0 +1,5 @@
+# yaml-language-server: $schema=https://harp-tech.org/draft-02/schema/registers.json
+device: ErrorTests
+firmwareVersion: "0.1"
+hardwareTargets: "1.0"
+registers: {}
\ No newline at end of file
diff --git a/tests/PayloadMarshal.cs b/tests/PayloadMarshal.cs
index 030099c..83caaa0 100644
--- a/tests/PayloadMarshal.cs
+++ b/tests/PayloadMarshal.cs
@@ -1,4 +1,4 @@
-#pragma warning disable IDE0005
+#pragma warning disable IDE0005
using System;
#pragma warning restore IDE0005
using Bonsai.Harp;
@@ -19,4 +19,4 @@ internal static void Write(ArraySegment segment, HarpVersion value)
segment.Array[segment.Offset] = (byte)value.Major.GetValueOrDefault();
segment.Array[segment.Offset + 1] = (byte)value.Minor.GetValueOrDefault();
}
-}
\ No newline at end of file
+}
diff --git a/tests/TestHelper.cs b/tests/TestHelper.cs
index 8c2281f..b12b307 100644
--- a/tests/TestHelper.cs
+++ b/tests/TestHelper.cs
@@ -1,4 +1,6 @@
-using Microsoft.VisualStudio.TestTools.UnitTesting;
+using System.CodeDom.Compiler;
+using System.Text;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
using YamlDotNet.Core;
namespace Harp.Generators.Tests;
@@ -52,4 +54,59 @@ public static void AssertExpectedOutput(string actual, string outputFileName)
}
}
}
+
+ static void AppendCompilerErrors(StringBuilder errorLog, CompilerErrorCollection errors)
+ {
+ foreach (CompilerError error in errors)
+ {
+ var warningString = error.IsWarning ? "warning" : "error";
+ errorLog.AppendLine($"{error.FileName}: {warningString}: {error.ErrorText}");
+ }
+ }
+
+ public static void AssertNoGeneratorErrors(CompilerErrorCollection errors)
+ {
+ if (errors.Count > 0)
+ {
+ var errorLog = new StringBuilder();
+ errorLog.AppendLine("Code generation has completed with errors:");
+ AppendCompilerErrors(errorLog, errors);
+ Assert.Fail(errorLog.ToString());
+ }
+ }
+
+ public static void AssertExpectedGeneratorErrors(CompilerErrorCollection errors, params string[] expectedErrors)
+ {
+ if (expectedErrors.Length == 0)
+ {
+ AssertNoGeneratorErrors(errors);
+ return;
+ }
+
+ var errorList = expectedErrors.ToList();
+ errorList.RemoveAll(errorText =>
+ {
+ foreach (CompilerError error in errors)
+ {
+ if (error.ErrorText.Contains(errorText))
+ return true;
+ }
+
+ return false;
+ });
+
+ if (errorList.Count > 0)
+ {
+ var errorLog = new StringBuilder();
+ errorLog.AppendLine("Expected code generation errors, but the following errors were not raised:");
+ foreach (var missingError in errorList)
+ errorLog.AppendLine(missingError);
+ if (errors.Count > 0)
+ {
+ errorLog.AppendLine("Code generation has completed with the following errors:");
+ AppendCompilerErrors(errorLog, errors);
+ }
+ Assert.Fail(errorLog.ToString());
+ }
+ }
}