Skip to content
Open
Show file tree
Hide file tree
Changes from 4 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
2 changes: 2 additions & 0 deletions docs/release-notes/.FSharp.Compiler.Service/11.0.0.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@

### Added

* #ELIF preprocessor directive ([PR #19045](https://github.com/dotnet/fsharp/pull/19045))

### Changed

* Parallel compilation stabilised and enabled by default ([PR #18998](https://github.com/dotnet/fsharp/pull/18998))
Expand Down
6 changes: 6 additions & 0 deletions src/Compiler/Service/ServiceLexing.fs
Original file line number Diff line number Diff line change
Expand Up @@ -408,6 +408,7 @@ module internal TokenClassifications =
| HASH_LINE _
| WARN_DIRECTIVE _
| HASH_IF _
| HASH_ELIF _
| HASH_ELSE _
| HASH_ENDIF _ -> (FSharpTokenColorKind.PreprocessorKeyword, FSharpTokenCharKind.WhiteSpace, FSharpTokenTriggerClass.None)

Expand Down Expand Up @@ -486,6 +487,7 @@ module internal LexerStateEncoding =
| HASH_LINE cont
| HASH_LIGHT cont
| HASH_IF(_, _, cont)
| HASH_ELIF(_, _, cont)
| HASH_ELSE(_, _, cont)
| HASH_ENDIF(_, _, cont)
| INACTIVECODE cont
Expand Down Expand Up @@ -613,6 +615,7 @@ module internal LexerStateEncoding =
match ifOrElse with
| IfDefIf, _ -> ()
| IfDefElse, _ -> ifdefStackBits <- (ifdefStackBits ||| (1 <<< ifdefStackCount))
| IfDefSkipRemaining, _ -> ifdefStackBits <- (ifdefStackBits ||| (1 <<< ifdefStackCount))

ifdefStackCount <- ifdefStackCount + 1

Expand Down Expand Up @@ -1051,6 +1054,7 @@ type FSharpLineTokenizer(lexbuf: UnicodeLexing.Lexbuf, maxLength: int option, fi
// for VS (which needs to recognize when user types ".").
match token with
| HASH_IF(m, lineStr, cont) when lineStr <> "" -> false, processHashIfLine m.StartColumn lineStr cont
| HASH_ELIF(m, lineStr, cont) when lineStr <> "" -> false, processHashIfLine m.StartColumn lineStr cont
| HASH_ELSE(m, lineStr, cont) when lineStr <> "" -> false, processHashEndElse m.StartColumn lineStr 4 cont
| HASH_ENDIF(m, lineStr, cont) when lineStr <> "" -> false, processHashEndElse m.StartColumn lineStr 5 cont
| WARN_DIRECTIVE(_, s, cont) -> false, processWarnDirective s leftc rightc cont
Expand Down Expand Up @@ -1301,6 +1305,7 @@ type FSharpLexerFlags =
type FSharpTokenKind =
| None
| HashIf
| HashElif
| HashElse
| HashEndIf
| WarnDirective
Expand Down Expand Up @@ -1515,6 +1520,7 @@ type FSharpToken =
| INFIX_STAR_DIV_MOD_OP "lxor" -> FSharpTokenKind.InfixLxor
| INFIX_STAR_DIV_MOD_OP "mod" -> FSharpTokenKind.InfixMod
| HASH_IF _ -> FSharpTokenKind.HashIf
| HASH_ELIF _ -> FSharpTokenKind.HashElif
| HASH_ELSE _ -> FSharpTokenKind.HashElse
| HASH_ENDIF _ -> FSharpTokenKind.HashEndIf
| WARN_DIRECTIVE _ -> FSharpTokenKind.WarnDirective
Expand Down
1 change: 1 addition & 0 deletions src/Compiler/Service/ServiceLexing.fsi
Original file line number Diff line number Diff line change
Expand Up @@ -369,6 +369,7 @@ type public FSharpLexerFlags =
type public FSharpTokenKind =
| None
| HashIf
| HashElif
| HashElse
| HashEndIf
| WarnDirective
Expand Down
75 changes: 75 additions & 0 deletions src/Compiler/Service/ServiceStructure.fs
Original file line number Diff line number Diff line change
Expand Up @@ -717,6 +717,49 @@ module Structure =
match directives with
| [] -> ()
| ConditionalDirectiveTrivia.If _ as ifDirective :: directives -> group directives (ifDirective :: stack) sourceLines
| ConditionalDirectiveTrivia.Elif(_, elifRange) as elifDirective :: directives ->
match stack with
| ConditionalDirectiveTrivia.If(_, ifRange) :: stack ->
let startLineIndex = elifRange.StartLine - 2

if startLineIndex >= 0 then
// start of #if until the end of the line directly above #elif
let range =
mkFileIndexRange
ifRange.FileIndex
ifRange.Start
(mkPos (elifRange.StartLine - 1) sourceLines[startLineIndex].Length)

{
Scope = Scope.HashDirective
Collapse = Collapse.Same
Range = range
CollapseRange = range
}
|> acc.Add

group directives (elifDirective :: stack) sourceLines
| ConditionalDirectiveTrivia.Elif(_, prevElifRange) :: stack ->
let startLineIndex = elifRange.StartLine - 2

if startLineIndex >= 0 then
// start of previous #elif until the end of the line directly above current #elif
let range =
mkFileIndexRange
prevElifRange.FileIndex
prevElifRange.Start
(mkPos (elifRange.StartLine - 1) sourceLines[startLineIndex].Length)

{
Scope = Scope.HashDirective
Collapse = Collapse.Same
Range = range
CollapseRange = range
}
|> acc.Add

group directives (elifDirective :: stack) sourceLines
| _ -> group directives stack sourceLines
| ConditionalDirectiveTrivia.Else elseRange as elseDirective :: directives ->
match stack with
| ConditionalDirectiveTrivia.If(_, ifRange) :: stack ->
Expand All @@ -738,6 +781,26 @@ module Structure =
}
|> acc.Add

group directives (elseDirective :: stack) sourceLines
| ConditionalDirectiveTrivia.Elif(_, elifRange) :: stack ->
let startLineIndex = elseRange.StartLine - 2

if startLineIndex >= 0 then
// start of #elif until the end of the line directly above #else
let range =
mkFileIndexRange
elifRange.FileIndex
elifRange.Start
(mkPos (elseRange.StartLine - 1) sourceLines[startLineIndex].Length)

{
Scope = Scope.HashDirective
Collapse = Collapse.Same
Range = range
CollapseRange = range
}
|> acc.Add

group directives (elseDirective :: stack) sourceLines
| _ -> group directives stack sourceLines
| ConditionalDirectiveTrivia.EndIf endIfRange :: directives ->
Expand All @@ -753,6 +816,18 @@ module Structure =
}
|> acc.Add

group directives stack sourceLines
| ConditionalDirectiveTrivia.Elif(_, elifRange) :: stack ->
let range = Range.startToEnd elifRange endIfRange

{
Scope = Scope.HashDirective
Collapse = Collapse.Same
Range = range
CollapseRange = range
}
|> acc.Add

group directives stack sourceLines
| ConditionalDirectiveTrivia.Else elseRange :: stack ->
let range = Range.startToEnd elseRange endIfRange
Expand Down
17 changes: 17 additions & 0 deletions src/Compiler/SyntaxTree/LexerStore.fs
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,23 @@ module IfdefStore =

store.Add(ConditionalDirectiveTrivia.If(expr, m))

let SaveElifHash (lexbuf: Lexbuf, lexed: string, expr: LexerIfdefExpression, range: range) =
let store = getStore lexbuf

let expr =
let rec visit (expr: LexerIfdefExpression) : IfDirectiveExpression =
match expr with
| LexerIfdefExpression.IfdefAnd(l, r) -> IfDirectiveExpression.And(visit l, visit r)
| LexerIfdefExpression.IfdefOr(l, r) -> IfDirectiveExpression.Or(visit l, visit r)
| LexerIfdefExpression.IfdefNot e -> IfDirectiveExpression.Not(visit e)
| LexerIfdefExpression.IfdefId id -> IfDirectiveExpression.Ident id

visit expr

let m = mkRangeWithoutLeadingWhitespace lexed range

store.Add(ConditionalDirectiveTrivia.Elif(expr, m))

let SaveElseHash (lexbuf: Lexbuf, lexed: string, range: range) =
let store = getStore lexbuf
let m = mkRangeWithoutLeadingWhitespace lexed range
Expand Down
2 changes: 2 additions & 0 deletions src/Compiler/SyntaxTree/LexerStore.fsi
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ module IfdefStore =

val SaveIfHash: lexbuf: Lexbuf * lexed: string * expr: LexerIfdefExpression * range: range -> unit

val SaveElifHash: lexbuf: Lexbuf * lexed: string * expr: LexerIfdefExpression * range: range -> unit

val SaveElseHash: lexbuf: Lexbuf * lexed: string * range: range -> unit

val SaveEndIfHash: lexbuf: Lexbuf * lexed: string * range: range -> unit
Expand Down
1 change: 1 addition & 0 deletions src/Compiler/SyntaxTree/ParseHelpers.fs
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ let rhs parseState i = rhs2 parseState i i
type LexerIfdefStackEntry =
| IfDefIf
| IfDefElse
| IfDefSkipRemaining // Used when an #if or #elif branch has been taken and we're skipping remaining branches

/// Represents the active #if/#else blocks
type LexerIfdefStackEntries = (LexerIfdefStackEntry * range) list
Expand Down
1 change: 1 addition & 0 deletions src/Compiler/SyntaxTree/ParseHelpers.fsi
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ val rhs: parseState: IParseState -> i: int -> range
type LexerIfdefStackEntry =
| IfDefIf
| IfDefElse
| IfDefSkipRemaining // Used when an #if or #elif branch has been taken and we're skipping remaining branches

type LexerIfdefStackEntries = (LexerIfdefStackEntry * range) list

Expand Down
1 change: 1 addition & 0 deletions src/Compiler/SyntaxTree/SyntaxTrivia.fs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ type IdentTrivia =
[<RequireQualifiedAccess; NoEquality; NoComparison>]
type ConditionalDirectiveTrivia =
| If of expr: IfDirectiveExpression * range: range
| Elif of expr: IfDirectiveExpression * range: range
| Else of range: range
| EndIf of range: range

Expand Down
1 change: 1 addition & 0 deletions src/Compiler/SyntaxTree/SyntaxTrivia.fsi
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ type IdentTrivia =
[<RequireQualifiedAccess; NoEquality; NoComparison>]
type ConditionalDirectiveTrivia =
| If of expr: IfDirectiveExpression * range: range
| Elif of expr: IfDirectiveExpression * range: range
| Else of range: range
| EndIf of range: range

Expand Down
87 changes: 85 additions & 2 deletions src/Compiler/lex.fsl
Original file line number Diff line number Diff line change
Expand Up @@ -1046,13 +1046,44 @@ rule token (args: LexArgs) (skip: bool) = parse
let tok = HASH_IF(m, lexed, LexCont.EndLine(args.ifdefStack, args.stringNest, contCase))
if skip then endline contCase args skip lexbuf else tok }

| anywhite* "#else" anywhite* ("//" anystring)?
| anywhite* "#elif" anywhite+ anystring
{ let m = lexbuf.LexemeRange
match args.ifdefStack with
| [] -> LEX_FAILURE (FSComp.SR.lexHashElseNoMatchingIf())
| (IfDefElse,_) :: _rest -> LEX_FAILURE (FSComp.SR.lexHashEndifRequiredForElse())
| (IfDefIf,_) :: rest ->
shouldStartLine args lexbuf m (FSComp.SR.lexHashElseMustBeFirst())
let lookup id = List.contains id args.conditionalDefines
let lexed = lexeme lexbuf
let _, expr = evalIfDefExpression lexbuf.StartPos lexbuf.ReportLibraryOnlyFeatures lexbuf.LanguageVersion lexbuf.StrictIndentation args lookup lexed
IfdefStore.SaveElifHash(lexbuf, lexed, expr, m)
// Change stack to IfDefSkipRemaining to indicate a branch has been taken and we're skipping the rest
args.ifdefStack <- (IfDefSkipRemaining,m) :: rest
let tok = HASH_ELIF(m, lexed, LexCont.EndLine(args.ifdefStack, args.stringNest, LexerEndlineContinuation.IfdefSkip(0, m)))
if skip then endline (LexerEndlineContinuation.IfdefSkip(0, m)) args skip lexbuf else tok
| (IfDefSkipRemaining,_) :: _ ->
// A branch has already been taken, skip remaining elif branches
shouldStartLine args lexbuf m (FSComp.SR.lexHashElseMustBeFirst())
let lookup id = List.contains id args.conditionalDefines
let lexed = lexeme lexbuf
let _, expr = evalIfDefExpression lexbuf.StartPos lexbuf.ReportLibraryOnlyFeatures lexbuf.LanguageVersion lexbuf.StrictIndentation args lookup lexed
IfdefStore.SaveElifHash(lexbuf, lexed, expr, m)
let tok = HASH_ELIF(m, lexed, LexCont.EndLine(args.ifdefStack, args.stringNest, LexerEndlineContinuation.IfdefSkip(0, m)))
if skip then endline (LexerEndlineContinuation.IfdefSkip(0, m)) args skip lexbuf else tok }

| anywhite* "#else" anywhite* ("//" anystring)?
{ let lexed = (lexeme lexbuf)
let m = lexbuf.LexemeRange
match args.ifdefStack with
| [] -> LEX_FAILURE (FSComp.SR.lexHashElseNoMatchingIf())
| (IfDefElse,_) :: _rest -> LEX_FAILURE (FSComp.SR.lexHashEndifRequiredForElse())
| (IfDefSkipRemaining,_) :: _ ->
// A branch has already been taken, skip the else branch
shouldStartLine args lexbuf m (FSComp.SR.lexHashElseMustBeFirst())
IfdefStore.SaveElseHash(lexbuf, lexed, m)
let tok = HASH_ELSE(m, lexed, LexCont.EndLine(args.ifdefStack, args.stringNest, LexerEndlineContinuation.IfdefSkip(0, m)))
if skip then endline (LexerEndlineContinuation.IfdefSkip(0, m)) args skip lexbuf else tok
| (IfDefIf,_) :: rest ->
let m = lexbuf.LexemeRange
shouldStartLine args lexbuf m (FSComp.SR.lexHashElseMustBeFirst())
args.ifdefStack <- (IfDefElse,m) :: rest
IfdefStore.SaveElseHash(lexbuf, lexed, m)
Expand All @@ -1076,8 +1107,14 @@ rule token (args: LexArgs) (skip: bool) = parse
let tok = fail args lexbuf (FSComp.SR.lexHashIfMustHaveIdent()) tok
if skip then token args skip lexbuf else tok }

| "#elif"
{ let tok = WHITESPACE (LexCont.Token (args.ifdefStack, args.stringNest))
let tok = fail args lexbuf (FSComp.SR.lexHashIfMustHaveIdent()) tok
if skip then token args skip lexbuf else tok }

// Let the parser deal with these invalid directives
| anywhite* "#if" ident_char+
| anywhite* "#elif" ident_char+
| anywhite* "#else" ident_char+
| anywhite* "#endif" ident_char+
| anywhite* "#light" ident_char+
Expand Down Expand Up @@ -1118,6 +1155,47 @@ and ifdefSkip (n: int) (m: range) (args: LexArgs) (skip: bool) = parse
let tok = INACTIVECODE(LexCont.EndLine(args.ifdefStack, args.stringNest, LexerEndlineContinuation.IfdefSkip(n+1, m)))
if skip then endline (LexerEndlineContinuation.IfdefSkip(n+1, m)) args skip lexbuf else tok }

| anywhite* "#elif" anywhite+ anystring
{ let m = lexbuf.LexemeRange

// If #elif is the first thing on the line then process it, otherwise ignore, because it is invalid (e.g. "(**) #elif ...")
if (m.StartColumn <> 0) then
if not skip then INACTIVECODE (LexCont.IfDefSkip(args.ifdefStack, args.stringNest, n, m))
else ifdefSkip n m args skip lexbuf
elif n = 0 then
match args.ifdefStack with
| []-> LEX_FAILURE (FSComp.SR.lexHashElseNoMatchingIf())
| (IfDefElse,_) :: _rest -> LEX_FAILURE (FSComp.SR.lexHashEndifRequiredForElse())
| (IfDefSkipRemaining,_) :: _ ->
// A branch has already been taken, skip remaining branches
let lexed = lexeme lexbuf
let lookup id = List.contains id args.conditionalDefines
let _, expr = evalIfDefExpression lexbuf.StartPos lexbuf.ReportLibraryOnlyFeatures lexbuf.LanguageVersion lexbuf.StrictIndentation args lookup lexed
IfdefStore.SaveElifHash(lexbuf, lexed, expr, m)
if not skip then INACTIVECODE(LexCont.EndLine(args.ifdefStack, args.stringNest, LexerEndlineContinuation.IfdefSkip(0, m)))
else endline (LexerEndlineContinuation.IfdefSkip(0, m)) args skip lexbuf
| (IfDefIf,_) :: rest ->
let lexed = lexeme lexbuf
let lookup id = List.contains id args.conditionalDefines
let isTrue, expr = evalIfDefExpression lexbuf.StartPos lexbuf.ReportLibraryOnlyFeatures lexbuf.LanguageVersion lexbuf.StrictIndentation args lookup lexed
IfdefStore.SaveElifHash(lexbuf, lexed, expr, m)
if isTrue then
// Condition is true, change stack to IfDefSkipRemaining and resume normal parsing
args.ifdefStack <- (IfDefSkipRemaining,m) :: rest
if not skip then HASH_ELIF(m, lexed, LexCont.EndLine(args.ifdefStack, args.stringNest, LexerEndlineContinuation.Token))
else endline LexerEndlineContinuation.Token args skip lexbuf
else
// Condition is false, continue skipping
if not skip then INACTIVECODE(LexCont.EndLine(args.ifdefStack, args.stringNest, LexerEndlineContinuation.IfdefSkip(0, m)))
else endline (LexerEndlineContinuation.IfdefSkip(0, m)) args skip lexbuf
else
let lexed = lexeme lexbuf
let lookup id = List.contains id args.conditionalDefines
let _, expr = evalIfDefExpression lexbuf.StartPos lexbuf.ReportLibraryOnlyFeatures lexbuf.LanguageVersion lexbuf.StrictIndentation args lookup lexed
IfdefStore.SaveElifHash(lexbuf, lexed, expr, m)
if not skip then INACTIVECODE(LexCont.EndLine(args.ifdefStack, args.stringNest, LexerEndlineContinuation.IfdefSkip(n, m)))
else endline (LexerEndlineContinuation.IfdefSkip(n, m)) args skip lexbuf }

| anywhite* "#else" anywhite* ("//" anystring)?
{ let lexed = (lexeme lexbuf)
let m = lexbuf.LexemeRange
Expand All @@ -1130,6 +1208,11 @@ and ifdefSkip (n: int) (m: range) (args: LexArgs) (skip: bool) = parse
match args.ifdefStack with
| []-> LEX_FAILURE (FSComp.SR.lexHashElseNoMatchingIf())
| (IfDefElse,_) :: _rest -> LEX_FAILURE (FSComp.SR.lexHashEndifRequiredForElse())
| (IfDefSkipRemaining,_) :: _ ->
// A branch has already been taken, skip remaining branches
IfdefStore.SaveElseHash(lexbuf, lexed, m)
if not skip then INACTIVECODE(LexCont.EndLine(args.ifdefStack, args.stringNest, LexerEndlineContinuation.IfdefSkip(0, m)))
else endline (LexerEndlineContinuation.IfdefSkip(0, m)) args skip lexbuf
| (IfDefIf,_) :: rest ->
let m = lexbuf.LexemeRange
IfdefStore.SaveElseHash(lexbuf, lexed, m)
Expand Down
2 changes: 1 addition & 1 deletion src/Compiler/pars.fsy
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,7 @@ let parse_error_rich = Some(fun (ctxt: ParseErrorContext<_>) ->
/* These are artificial */
%token <string> LEX_FAILURE
%token <ParseHelpers.LexerContinuation> COMMENT WHITESPACE HASH_LINE HASH_LIGHT INACTIVECODE LINE_COMMENT STRING_TEXT EOF
%token <range * string * ParseHelpers.LexerContinuation> HASH_IF HASH_ELSE HASH_ENDIF WARN_DIRECTIVE
%token <range * string * ParseHelpers.LexerContinuation> HASH_IF HASH_ELIF HASH_ELSE HASH_ENDIF WARN_DIRECTIVE

%start signatureFile implementationFile interaction typedSequentialExprEOF typEOF
%type <SynExpr> typedSequentialExprEOF
Expand Down
Loading
Loading