Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 15 additions & 16 deletions src/FirmwareGenerator.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
using System.Collections;
using System.CodeDom.Compiler;
using System.Collections;
using System.Collections.Generic;
using System.IO;

namespace Harp.Generators;

Expand All @@ -16,6 +16,7 @@ public class FirmwareGenerator
readonly AppRegs _appRegsTemplate = new();
readonly AppRegsImpl _appRegsImplTemplate = new();
readonly Interrupts _interruptsTemplate = new();
readonly CompilerErrorCollection errors = [];

/// <summary>
/// Initializes a new instance of the <see cref="FirmwareGenerator"/> class with the
Expand All @@ -30,22 +31,20 @@ public FirmwareGenerator(DeviceInfo deviceMetadata, Dictionary<string, PortPinIn
{ "DeviceMetadata", deviceMetadata },
{ "PortPinMetadata", portPinMetadata }
};
_appTemplate.Session = session;
_appImplTemplate.Session = session;
_appFuncsTemplate.Session = session;
_appFuncsImplTemplate.Session = session;
_appRegsTemplate.Session = session;
_appRegsImplTemplate.Session = session;
_interruptsTemplate.Session = session;
_appTemplate.Initialize();
_appImplTemplate.Initialize();
_appFuncsTemplate.Initialize();
_appFuncsImplTemplate.Initialize();
_appRegsTemplate.Initialize();
_appRegsImplTemplate.Initialize();
_interruptsTemplate.Initialize();
_appTemplate.Initialize(FirmwareHeaders.AppFileName, errors, session);
_appImplTemplate.Initialize(FirmwareImplementation.AppFileName, errors, session);
_appFuncsTemplate.Initialize(FirmwareHeaders.AppFuncsFileName, errors, session);
_appFuncsImplTemplate.Initialize(FirmwareImplementation.AppFuncsFileName, errors, session);
_appRegsTemplate.Initialize(FirmwareHeaders.AppRegsFileName, errors, session);
_appRegsImplTemplate.Initialize(FirmwareImplementation.AppRegsFileName, errors, session);
_interruptsTemplate.Initialize(FirmwareImplementation.InterruptsFileName, errors, session);
}

/// <summary>
/// Gets the collection of errors emitted during the code generation process.
/// </summary>
public CompilerErrorCollection Errors => errors;

/// <summary>
/// Generates firmware header files complying with the specified metadata file.
/// </summary>
Expand Down
6 changes: 3 additions & 3 deletions src/Interface.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -655,7 +655,7 @@ public static string GetPayloadMemberValueFormatter(
{
if (member.HasConverter)
expression = $"FormatPayload{name}({expression})";

if (member.Length > 0)
return expression;

Expand Down Expand Up @@ -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)));
Expand Down
16 changes: 10 additions & 6 deletions src/InterfaceGenerator.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
using System.Collections;
using System.CodeDom.Compiler;
using System.Collections;
using System.Collections.Generic;
using System.IO;

namespace Harp.Generators;

Expand All @@ -11,6 +11,7 @@ public sealed class InterfaceGenerator
{
readonly Device _deviceTemplate = new();
readonly AsyncDevice _asyncDeviceTemplate = new();
readonly CompilerErrorCollection errors = [];

/// <summary>
/// Initializes a new instance of the <see cref="InterfaceGenerator"/> class with the
Expand All @@ -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);
}

/// <summary>
/// Gets the collection of errors emitted during the code generation process.
/// </summary>
public CompilerErrorCollection Errors => errors;

/// <summary>
/// Generates a device interface implementation complying with the specified metadata file.
/// </summary>
Expand Down
66 changes: 40 additions & 26 deletions src/TemplateBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,112 +7,126 @@ namespace Harp.Generators;

internal abstract class TemplateBase
{
private StringBuilder builder;
private StringBuilder builder;
private CompilerErrorCollection errors;
private string currentIndent = string.Empty;
private Stack<int> indents;

public virtual IDictionary<string, object> 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<int> Indents => indents ??= [];

public ToStringInstanceHelper ToStringHelper { get; } = new();

public abstract string TransformText();

public virtual void Initialize() { }


public void Initialize(string fileName, CompilerErrorCollection errors, Dictionary<string, object> 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)
throw new ArgumentNullException(nameof(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();
}
}
Expand Down
4 changes: 3 additions & 1 deletion tests/FirmwareGeneratorTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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");
Expand All @@ -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);
Expand Down
1 change: 1 addition & 0 deletions tests/InterfaceGeneratorTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
8 changes: 8 additions & 0 deletions tests/Metadata/errors.ios.yml
Original file line number Diff line number Diff line change
@@ -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
5 changes: 5 additions & 0 deletions tests/Metadata/errors.yml
Original file line number Diff line number Diff line change
@@ -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: {}
4 changes: 2 additions & 2 deletions tests/PayloadMarshal.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
#pragma warning disable IDE0005
#pragma warning disable IDE0005
using System;
#pragma warning restore IDE0005
using Bonsai.Harp;
Expand All @@ -19,4 +19,4 @@ internal static void Write(ArraySegment<byte> segment, HarpVersion value)
segment.Array[segment.Offset] = (byte)value.Major.GetValueOrDefault();
segment.Array[segment.Offset + 1] = (byte)value.Minor.GetValueOrDefault();
}
}
}
59 changes: 58 additions & 1 deletion tests/TestHelper.cs
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -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());
}
}
}
Loading