Skip to content
Open
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
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -111,10 +111,12 @@ UpgradeLog*.XML

# FFmpeg binaries (download via FFmpeg/download-ffmpeg.ps1)
FFmpeg/bin/
FFmpeg/sample.*

# JetBrains Rider
.idea/
*.sln.iml

# Claude
.claude/
.claude/
CLAUDE.md
125 changes: 83 additions & 42 deletions FFmpeg.AutoGen.Abstractions/generated/ffmpeg.macros.g.cs

Large diffs are not rendered by default.

88 changes: 88 additions & 0 deletions FFmpeg.AutoGen.ClangMacroParser.Test/ParserTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -140,5 +140,93 @@ public void FunctionCallWithDigitPrefixedArg()
CastExpression<VariableExpression>(args[1], y => Assert.AreEqual("RGB0", y.Name));
});
}

[TestMethod]
public void CompoundLiteral()
{
var e = Parser.Parse("(AVRational){1, 1000000}");
CastExpression<CompoundLiteralExpression>(e,
x =>
{
Assert.AreEqual("AVRational", x.TypeName);
Assert.AreEqual(2, x.Initializer.Fields.Count);
Assert.IsNull(x.Initializer.Fields[0].Name);
CastExpression<ConstantExpression>(x.Initializer.Fields[0].Value, y => Assert.AreEqual(1, y.Value));
CastExpression<ConstantExpression>(x.Initializer.Fields[1].Value, y => Assert.AreEqual(1000000, y.Value));
});
}

[TestMethod]
public void InitializerList()
{
var e = Parser.Parse("{ 0, 2, { 3 }, 0 }");
CastExpression<InitializerListExpression>(e,
x =>
{
Assert.AreEqual(4, x.Fields.Count);
CastExpression<ConstantExpression>(x.Fields[0].Value, y => Assert.AreEqual(0, y.Value));
CastExpression<ConstantExpression>(x.Fields[1].Value, y => Assert.AreEqual(2, y.Value));
CastExpression<InitializerListExpression>(x.Fields[2].Value, y => Assert.AreEqual(1, y.Fields.Count));
CastExpression<ConstantExpression>(x.Fields[3].Value, y => Assert.AreEqual(0, y.Value));
});
}

[TestMethod]
public void CCommentSkipping()
{
var e = Parser.Parse("{ /* .order */ 0, /* .nb */ 2 }");
CastExpression<InitializerListExpression>(e,
x =>
{
Assert.AreEqual(2, x.Fields.Count);
CastExpression<ConstantExpression>(x.Fields[0].Value, y => Assert.AreEqual(0, y.Value));
CastExpression<ConstantExpression>(x.Fields[1].Value, y => Assert.AreEqual(2, y.Value));
});
}

[TestMethod]
public void NullHandling()
{
var e = Parser.Parse("NULL");
CastExpression<ConstantExpression>(e, x => Assert.AreEqual(0, x.Value));
}

[TestMethod]
public void DesignatedInitializer()
{
var e = Parser.Parse("{ .order = 0, .nb_channels = 2 }");
CastExpression<InitializerListExpression>(e,
x =>
{
Assert.AreEqual(2, x.Fields.Count);
Assert.AreEqual("order", x.Fields[0].Name);
Assert.AreEqual("nb_channels", x.Fields[1].Name);
CastExpression<ConstantExpression>(x.Fields[0].Value, y => Assert.AreEqual(0, y.Value));
CastExpression<ConstantExpression>(x.Fields[1].Value, y => Assert.AreEqual(2, y.Value));
});
}

[TestMethod]
public void NestedDesignatedInitializer()
{
var e = Parser.Parse("{ .order = AV_CHANNEL_ORDER_NATIVE, .nb_channels = 1, .u.mask = { AV_CH_LAYOUT_MONO }, .opaque = NULL }");
CastExpression<InitializerListExpression>(e,
x =>
{
Assert.AreEqual(4, x.Fields.Count);
Assert.AreEqual("order", x.Fields[0].Name);
Assert.AreEqual("nb_channels", x.Fields[1].Name);
Assert.AreEqual("u.mask", x.Fields[2].Name);
Assert.AreEqual("opaque", x.Fields[3].Name);
CastExpression<VariableExpression>(x.Fields[0].Value, y => Assert.AreEqual("AV_CHANNEL_ORDER_NATIVE", y.Name));
CastExpression<ConstantExpression>(x.Fields[1].Value, y => Assert.AreEqual(1, y.Value));
CastExpression<InitializerListExpression>(x.Fields[2].Value, y =>
{
Assert.AreEqual(1, y.Fields.Count);
CastExpression<VariableExpression>(y.Fields[0].Value, z => Assert.AreEqual("AV_CH_LAYOUT_MONO", z.Name));
});
CastExpression<ConstantExpression>(x.Fields[3].Value, y => Assert.AreEqual(0, y.Value));
});
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
namespace FFmpeg.AutoGen.ClangMacroParser.Expressions
{
/// <summary>
/// Represents a C99 compound literal: (TypeName){ expr, expr } or (TypeName){ .field = expr }
/// </summary>
public class CompoundLiteralExpression : IExpression
{
public CompoundLiteralExpression(string typeName, InitializerListExpression initializer)
{
TypeName = typeName;
Initializer = initializer;
}

public string TypeName { get; }

public InitializerListExpression Initializer { get; }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
using System.Collections.Generic;

namespace FFmpeg.AutoGen.ClangMacroParser.Expressions
{
/// <summary>
/// Represents a C initializer list: { expr, expr } or { .field = expr, .field = expr }
/// </summary>
public class InitializerListExpression : IExpression
{
public InitializerListExpression(IEnumerable<InitializerField> fields)
{
Fields = new List<InitializerField>(fields);
}

public List<InitializerField> Fields { get; }
}

public class InitializerField
{
/// <summary>
/// Field name for designated initializers (.field = value). Null for positional.
/// </summary>
public string? Name { get; set; }

/// <summary>
/// The value expression.
/// </summary>
public IExpression Value { get; set; }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,35 +32,40 @@ public static class OperationTypeExtensions

public static int GetPrecedence(this OperationType operationType) => OperationPrecedence[operationType];

public static OperationType ToOperationType(this string value)
public static bool TryToOperationType(this string value, out OperationType result)
{
switch (value)
{
case "+": return OperationType.Add;
case "/": return OperationType.Divide;
case "%": return OperationType.Modulo;
case "*": return OperationType.Multiply;
case "^": return OperationType.Power;
case "-": return OperationType.Subtract;
case "&": return OperationType.And;
case "|": return OperationType.Or;
case "~": return OperationType.ExclusiveOr;
case "<<": return OperationType.LeftShift;
case ">>": return OperationType.RightShift;
case "&&": return OperationType.AndAlso;
case "||": return OperationType.OrElse;
case "==": return OperationType.Equal;
case "!=": return OperationType.NotEqual;
case ">=": return OperationType.GreaterThanOrEqual;
case ">": return OperationType.GreaterThan;
case "<": return OperationType.LessThan;
case "<=": return OperationType.LessThanOrEqual;
case "##": return OperationType.TokenConcat;
default:
throw new ArgumentOutOfRangeException(nameof(value));
case "+": result = OperationType.Add; return true;
case "/": result = OperationType.Divide; return true;
case "%": result = OperationType.Modulo; return true;
case "*": result = OperationType.Multiply; return true;
case "^": result = OperationType.Power; return true;
case "-": result = OperationType.Subtract; return true;
case "&": result = OperationType.And; return true;
case "|": result = OperationType.Or; return true;
case "~": result = OperationType.ExclusiveOr; return true;
case "<<": result = OperationType.LeftShift; return true;
case ">>": result = OperationType.RightShift; return true;
case "&&": result = OperationType.AndAlso; return true;
case "||": result = OperationType.OrElse; return true;
case "==": result = OperationType.Equal; return true;
case "!=": result = OperationType.NotEqual; return true;
case ">=": result = OperationType.GreaterThanOrEqual; return true;
case ">": result = OperationType.GreaterThan; return true;
case "<": result = OperationType.LessThan; return true;
case "<=": result = OperationType.LessThanOrEqual; return true;
case "##": result = OperationType.TokenConcat; return true;
default: result = default; return false;
}
}

public static OperationType ToOperationType(this string value)
{
if (TryToOperationType(value, out var result)) return result;
throw new ArgumentOutOfRangeException(nameof(value));
}

public static string ToOperationTypeString(this OperationType operationType)
{
switch (operationType)
Expand Down
60 changes: 57 additions & 3 deletions FFmpeg.AutoGen.ClangMacroParser/Parser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -80,15 +80,65 @@ IExpression Unary()
return new UnaryExpression(operationType, Expression());
}

IExpression InitializerList()
{
Read(); // skip {
var fields = new List<InitializerField>();

while (CanRead() && !Current().IsPunctuator("}"))
{
string? fieldName = null;

// Check for designated initializer: .field = value
// After tokenizer, ".field" is a single Identifier token starting with "."
if (Current().IsIdentifier() && Current().Value.StartsWith(".") &&
i + 1 < tokens.Length && tokens[i + 1].IsOperator() && tokens[i + 1].Value == "=")
{
fieldName = Current().Value.Substring(1); // remove leading "."
Read(); // skip .field
Read(); // skip =
}

fields.Add(new InitializerField { Name = fieldName, Value = Expression() });

if (CanRead() && Current().IsPunctuator(",")) Read();
}

if (CanRead() && Current().IsPunctuator("}")) Read(); // skip }

return new InitializerListExpression(fields);
}

IExpression Atomic()
{
if (Current().IsPunctuator("{")) return InitializerList();
if (Current().IsPunctuator("(")) return InParentheses(Expression);
if (Current().IsConstant() || Current().IsString()) return Constant();
if (Current().IsIdentifier()) return Variable();
if (Current().IsIdentifier())
{
// NULL → null constant
if (Current().Value == "NULL") { Read(); return new ConstantExpression(0); }
return Variable();
}
throw new NotSupportedException();
}

bool IsCast() => IsSequenceOf(x => x.IsPunctuator("("), x => x.IsKeyword() || x.IsIdentifier(), x => x.IsPunctuator(")"));
bool IsTypeInParentheses() => IsSequenceOf(
x => x.IsPunctuator("("),
x => x.IsKeyword() || x.IsIdentifier(),
x => x.IsPunctuator(")"));

bool IsCast() => IsTypeInParentheses();

bool IsCompoundLiteral() => IsTypeInParentheses()
&& i + 3 < tokens.Length && tokens[i + 3].IsPunctuator("{");

IExpression CompoundLiteral()
{
var typeName = InParentheses(() => Read().Value);
var init = (InitializerListExpression)InitializerList();
return new CompoundLiteralExpression(typeName, init);
}

IExpression Cast() => new CastExpression(InParentheses(() => Read().Value), NoneAtomic());

Expand All @@ -98,18 +148,22 @@ IExpression NoneAtomic()
{
if (IsSequenceOf(x => x.IsIdentifier(), x => x.IsPunctuator("("))) return Call();
if (Current().IsOperator()) return Unary();
if (IsCompoundLiteral()) return CompoundLiteral();
if (IsCast()) return Cast();
return Atomic();
}

throw new NotSupportedException();
}

bool IsKnownBinaryOperator() =>
CanRead() && Current().IsOperator() && Current().Value.TryToOperationType(out _);

IExpression MaybeBinary(IExpression left, int precedence = int.MaxValue)
{
while (true)
{
if (CanRead() && Current().IsOperator())
if (IsKnownBinaryOperator())
{
var operationType = Current().Value.ToOperationType();
var currentPrecedence = operationType.GetPrecedence();
Expand Down
16 changes: 16 additions & 0 deletions FFmpeg.AutoGen.ClangMacroParser/Tokenization/Tokenizer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,22 @@ Token Punctuator()
var c = Current();

if (Separators.Contains(c)) Skip(Separators.Contains);
else if (c == '/' && i + 1 < characters.Length && characters[i + 1] == '*')
{
// Skip C-style comments /* ... */
Read(); Read(); // skip /*
while (CanRead() && !(Current() == '*' && i + 1 < characters.Length && characters[i + 1] == '/'))
Read();
if (CanRead()) { Read(); Read(); } // skip */
}
else if (c == '.' && i + 1 < characters.Length && IsIdentifierStart(characters[i + 1]))
{
// Designated initializer field: .field or .u.mask
var startPos = i;
Read(); // consume '.'
var value = "." + new string(YieldWhile(IsIdentifierStart, IsId).ToArray());
yield return new Token(TokenType.Identifier, value, startPos, value.Length);
}
else if (IsNumberStart(c))
{
var num = Number();
Expand Down
Loading