diff --git a/src/Core/Hll/C/CDirectiveLexer.cs b/src/Core/Hll/C/CDirectiveLexer.cs index a8b7ea6df6..9f1d70fe5b 100644 --- a/src/Core/Hll/C/CDirectiveLexer.cs +++ b/src/Core/Hll/C/CDirectiveLexer.cs @@ -39,16 +39,23 @@ public class CDirectiveLexer private readonly Dictionary directives = new() { { "define", Directive.Define }, + { "ifdef", Directive.Ifdef }, + { "ifndef", Directive.Ifndef }, + { "else", Directive.Else }, + { "endif", Directive.Endif }, { "line", Directive.Line }, { "pragma", Directive.Pragma }, - { "__pragma", Directive.Pragma } + { "__pragma", Directive.Pragma }, + { "undef", Directive.Undef }, }; private readonly ParserState parserState; private readonly Dictionary> macros; private readonly CLexer lexer; + private readonly Stack ifdefs; private IEnumerator? expandedTokens; private State state; + private bool ignoreTokens; public CDirectiveLexer(ParserState state, CLexer lexer) { @@ -56,6 +63,8 @@ public CDirectiveLexer(ParserState state, CLexer lexer) this.lexer = lexer; this.macros = new(); this.state = State.StartLine; + this.ifdefs = new Stack(); + this.ignoreTokens = false; } public int LineNumber => lexer.LineNumber; @@ -70,9 +79,14 @@ private enum State private enum Directive { Define, + Else, + Endif, + Ifdef, + Ifndef, Line, Pragma, __Pragma, + Undef, } public CToken Read() @@ -98,6 +112,11 @@ public CToken Read() break; default: state = State.InsideLine; + if (ignoreTokens) + { + token = ReadToken(); + break; + } return token; } break; @@ -118,6 +137,11 @@ public CToken Read() token = ReadToken(); break; } + if (ignoreTokens) + { + token = ReadToken(); + break; + } return token; case CTokenType.__Pragma: Expect(CTokenType.LParen); @@ -126,6 +150,11 @@ public CToken Read() state = State.InsideLine; break; default: + if (ignoreTokens) + { + token = ReadToken(); + break; + } return token; } break; @@ -156,8 +185,37 @@ public CToken Read() token = ReadDefine(); state = State.StartLine; break; + case Directive.Ifdef: + var ifdefVar = (string) Expect(CTokenType.Id)!; + ifdefs.Push(ignoreTokens); + ignoreTokens = !IsDefined(ifdefVar); + token = ReadToken(); //$TODO: read to end of line + state = State.StartLine; + break; + case Directive.Ifndef: + ifdefVar = (string) Expect(CTokenType.Id)!; + ifdefs.Push(ignoreTokens); + ignoreTokens = IsDefined(ifdefVar); + token = ReadToken(); //$TODO: read to end of line + state = State.StartLine; + break; + case Directive.Endif: + if (ifdefs.Count == 0) + throw new FormatException($"Unbalanced #if/#endif"); + ignoreTokens = ifdefs.Pop(); + token = ReadToken(); //$TODO: read to end of line + state = State.StartLine; + break; } break; + case CTokenType.Else: + if (ifdefs.Count == 0) + throw new FormatException($"Unbalanced #if/#else"); + state = State.StartLine; + ignoreTokens = !ignoreTokens; + token = ReadToken(); //$TODO: read to end of line + state = State.StartLine; + break; default: throw new FormatException($"Unexpected token {token.Type} on line {lexer.LineNumber}."); } @@ -331,5 +389,10 @@ public virtual CToken ReadPragma(string pragma) throw new FormatException(string.Format("Expected '{0}' but got '{1}' on line {2}.", expected, actualToken.Type, lexer.LineNumber)); return actualToken.Value; } + + private bool IsDefined(string preprocessorVar) + { + return macros.ContainsKey(preprocessorVar); + } } } diff --git a/src/UnitTests/Core/Hll/C/CDirectiveLexerTests.cs b/src/UnitTests/Core/Hll/C/CDirectiveLexerTests.cs index 381e8d615e..4c47f4aa92 100644 --- a/src/UnitTests/Core/Hll/C/CDirectiveLexerTests.cs +++ b/src/UnitTests/Core/Hll/C/CDirectiveLexerTests.cs @@ -206,5 +206,63 @@ public void CDirectiveLexer_msvc_pragma_pack_push() Assert.AreEqual(CTokenType.EOF, lexer.Read().Type); Assert.AreEqual(8, state.Alignment); } + + [Test] + public void CDirectiveLexer_ifdef() + { + LexMsvc(@" +#define DOIT +#ifdef DOIT +foo +#endif +bar +"); + Assert.AreEqual("foo", lexer.Read().Value); + Assert.AreEqual("bar", lexer.Read().Value); + Assert.AreEqual(CTokenType.EOF, lexer.Read().Type); + } + + [Test] + public void CDirectiveLexer_ifdef_not_defined() + { + LexMsvc(@" +#ifdef DOIT +foo +#endif +bar +"); + Assert.AreEqual("bar", lexer.Read().Value); + Assert.AreEqual(CTokenType.EOF, lexer.Read().Type); + } + + [Test] + public void CDirectiveLexer_ifndef() + { + LexMsvc(@" +#ifndef DOIT +foo +#endif +bar +"); + Assert.AreEqual("foo", lexer.Read().Value); + Assert.AreEqual("bar", lexer.Read().Value); + Assert.AreEqual(CTokenType.EOF, lexer.Read().Type); + } + + [Test] + public void CDirectiveLexer_if_else() + { + LexMsvc(@" +#ifdef DOIT +doit +#else +dontdoit +#endif +bar +"); + Assert.AreEqual("dontdoit", lexer.Read().Value); + Assert.AreEqual("bar", lexer.Read().Value); + Assert.AreEqual(CTokenType.EOF, lexer.Read().Type); + } } }