#functional-programming #f# #fparsec
#функциональное программирование #f# #fparsec
Вопрос:
Я пишу рекурсивный анализатор с помощью FParsec, поэтому я создаю фиктивный анализатор и ссылочную ячейку createParserForwardedToRef
примерно так:
let pstatement, pstatementref = createParserForwardedToRef<Statement, Unit>()
Ссылочной ячейке в конечном итоге «присваивается» следующее:
do pstatementref := choice [
pread
pdisplay
pset
pcompute
pif
pwhile
]
Когда я тестирую анализатор pstatement
из файла, в котором создана ссылочная ячейка, и присваиваю указанное выше значение с помощью следующего кода…
let test p str =
match runParserOnFile p () str Encoding.ASCII with
| Success(result, _, _) -> printfn "Success: %A" result
| Failure(errorMsg, _, _) -> printfn "Failure: %s" errorMsg
[<EntryPoint>]
let main argv =
argv
|> String.concat " "
|> test (manyTill pstatement (pword "HALT"))
0
… это работает хорошо, и я получаю успешные результаты.
Однако, когда я пытаюсь запустить точно такую же тестовую функцию, на этот раз из другого проекта (называемого интерпретатором), который ссылается на проект, в котором pstatement
определен (называемый синтаксическим анализатором, после удаления основной функции из Program.fs в синтаксическом анализаторе), я получаю следующую ошибку:
Unhandled exception. System.NullReferenceException: Object reference not set to an instance of an object.
at FParsec.Primitives.manyTill@890.Invoke(CharStream`1 stream)
at FParsec.CharParsers.applyParser[Result,UserState](FSharpFunc`2 parser, CharStream`1 stream)
at FParsec.CharParsers.runParserOnFile[a,u](FSharpFunc`2 parser, u ustate, String path, Encoding encoding)
at Program.test[a](FSharpFunc`2 p, String str) in /home/rosalogia/Projects/FSharp/PCParser/src/Interpreter/Program.fs:line 16
at Program.main(String[] argv) in /home/rosalogia/Projects/FSharp/PCParser/src/Interpreter/Program.fs:line 32
… это означает, что ссылочной ячейке никогда не присваивается какое-либо значение, однако код, который это делает, не был удален из проекта синтаксического анализатора.
Существует ли ограничение ссылочных ячеек, о котором я не знаю, что вызывает эту проблему? Поскольку в файле, в котором определена ссылочная ячейка, все работает нормально, можно с уверенностью заключить, что проблема заключается в доступе к ее значению из файла или проекта за пределами того, в котором оно определено.
Редактировать:
Вот более полный пример того, как все настроено
Синтаксический анализатор / Library.fs
namespace PCParser
module Parser =
let pstatement, pstatementref = createParserForwardedToRef<Statement, Unit>()
let pread = ...
let pdisplay = ...
...
do pstatementref := choice [
pread
pdisplay
pset
pcompute
pif
pwhile
]
Интерпретатор / Program.fs
open PCParser.Parser
open FParsec
open System.Text
let test p str =
match runParserOnFile p () str Encoding.ASCII with
| Success(result, _, _) -> printfn "Success: %A" result
| Failure(errorMsg, _, _) -> printfn "Failure: %s" errorMsg
[<EntryPoint>]
let main argv =
argv
|> String.concat " "
|> test (manyTill pstatement (pword "HALT"))
0
Комментарии:
1. Где именно находится вызов, который присваивает ссылку? Как вы это выполняете?
2. Вызов, который присваивает ссылку, находится в проекте синтаксического анализатора и модуле, который я
open
использую из проекта интерпретатора. Я предполагал, что назначение будет выполняться по умолчанию при открытии модуля.3. Не могли бы вы опубликовать минимальное воспроизведение, пожалуйста? Из вашего объяснения неясно, что содержится / где вложено.
4. Я попытался уточнить в редактировании, как все настроено. Минимальный пример Томаса работает как минимальное воспроизведение моей проблемы, однако я все еще не уверен, как ее можно решить. Я только недавно изменил проект синтаксического анализатора на classlib, но проблема сохраняется.
Ответ №1:
Я предполагаю, что это связано с тем, что проект, на который вы ссылаетесь (который определяет анализатор), компилируется как исполняемый файл, а не как библиотека.
Вот минимальный пример, который демонстрирует аналогичное поведение. Скажем project1
, мы имеем следующее:
module Xyz
let r = ref None
do r := Some 42
И у нас есть project2
эти ссылки project1
и имеет следующий код:
printfn "GOT: %A" Xyz.r
Если вы скомпилируете project1
как исполняемый файл, выполняемый код будет напечатан GOT: <null>
, но если вы измените тип вывода project1
на библиотеку классов (dll), тогда этот код будет напечатан GOT: { contents=Some 42 }
.
Причина в том, что компилятор F # по-разному компилирует код инициализации для библиотек классов и исполняемых файлов. Для исполняемых файлов предполагается, что исполняемый файл будет выполнен, и поэтому он запускает код инициализации в main
функции. Для библиотек это не может предполагать, и поэтому он помещает проверки в статические конструкторы.
Чтобы исправить это, вам либо нужно скомпилировать свой код как библиотеку классов, либо вы можете добавить какую-либо пользовательскую функцию инициализации, которую тестовый проект может вызывать перед запуском тестов (и из которой можно вызывать main
)
Комментарии:
1. Это полезная информация, и я определенно пропустил это. Как мы можем гарантировать, что проект скомпилирован как classlib, помимо его инициализации с
dotnet new classlib -lang F# ...
помощью? Я действительно пытался воссоздать проект синтаксического анализатора таким образом, но безуспешно, так что, возможно, я что-то упускаю.2. Я думаю
dotnet new
, что генерирует.fsproj
файл — в этом файле вы можете установить<OutputType>
значениеExe
(для исполняемого файла) илиLibrary
(для библиотеки)