diff --git a/src/macro/Parser.yy b/src/macro/Parser.yy
index b6bf3893a6204a03fa717f1c1f0eb6f463586ad6..290905702d53dbc8078a4e536bc3853025851822 100644
--- a/src/macro/Parser.yy
+++ b/src/macro/Parser.yy
@@ -59,7 +59,7 @@ using namespace macro;
 %token FOR ENDFOR IF IFDEF IFNDEF ELSEIF ELSE ENDIF TRUE FALSE
 %token INCLUDE INCLUDEPATH DEFINE EQUAL D_ECHO ERROR
 %token COMMA LPAREN RPAREN LBRACKET RBRACKET WHEN
-%token BEGIN_EVAL END_EVAL ECHOMACROVARS SAVE
+%token BEGIN_EVAL END_EVAL ECHOMACROVARS SAVE LINE
 
 %token EXP LOG LN LOG10 SIN COS TAN ASIN ACOS ATAN
 %token SQRT CBRT SIGN MAX MIN FLOOR CEIL TRUNC SUM MOD
@@ -145,6 +145,12 @@ directive_one_line : INCLUDE expr
                      { $$ = make_shared<EchoMacroVars>(true, driver.env, @$); }
                    | ECHOMACROVARS LPAREN SAVE RPAREN name_list
                      { $$ = make_shared<EchoMacroVars>(true, $5, driver.env, @$); }
+                   | LINE QUOTED_STRING NUMBER
+                     {
+                       // `@#line` is ignored; adjust newlines in output to accord
+                       auto l = static_cast<Tokenizer::parser::location_type>(@$);
+                       $$ = make_shared<TextNode>(string(l.end.line - l.begin.line + 1, '\n'), driver.env, @$);
+                     }
                    ;
 
 name_list : NAME
diff --git a/src/macro/Tokenizer.ll b/src/macro/Tokenizer.ll
index e93b4f791481588e9d8eded081611deff6c8ae25..4d646878c34bc57c91432b442a54765d5a5bdf7d 100644
--- a/src/macro/Tokenizer.ll
+++ b/src/macro/Tokenizer.ll
@@ -64,6 +64,7 @@ CONT \\\\{SPC}*
   yylloc->step();
 %}
 
+<directive>line            { BEGIN(expr); return token::LINE; }
 <directive>include         { BEGIN(expr); return token::INCLUDE; }
 <directive>includepath     { BEGIN(expr); return token::INCLUDEPATH; }
 <directive>define          { BEGIN(expr); return token::DEFINE; }