diff --git a/src/PowerFlowData.jl b/src/PowerFlowData.jl index 5d5bb4e..4857c36 100644 --- a/src/PowerFlowData.jl +++ b/src/PowerFlowData.jl @@ -1,7 +1,7 @@ module PowerFlowData using DocStringExtensions -using InlineStrings: InlineString1, InlineString3, InlineString15 +using InlineStrings: InlineString1, InlineString3, InlineString15, InlineString31 using Parsers: Parsers, xparse, checkdelim! using Parsers: codes, eof, invalid, invaliddelimiter, newline, valueok, peekbyte using PrettyTables: pretty_table diff --git a/src/parsing.jl b/src/parsing.jl index 852fd2f..4fb3ab8 100644 --- a/src/parsing.jl +++ b/src/parsing.jl @@ -23,6 +23,18 @@ const OPTIONS_SPACE = Parsers.Options( ignorerepeated=true, wh1=0x00, ) +# These Options are for supporting extracting end-of-line comments like +# `/* [STBC 1 ] */` which we want to parse to `STBC 1`. +const OPTIONS_COMMENT = Parsers.Options( + sentinel=missing, + quoted=true, + openquotechar="/* [", + closequotechar="] */", + stripquoted=true, + delim=' ', + ignorerepeated=true, + wh1=0x00, +) @inline getoptions(delim::Char) = ifelse(delim === ',', OPTIONS_COMMA, OPTIONS_SPACE) @@ -244,6 +256,51 @@ end return block end +### +### Buses +### + +# In v30 files, we've seen trailing end-of-line comments. These can be present +# in any section, but so far we have only had a use-case for the comments in the +# buses section. +# See https://github.com/nickrobinson251/PowerFlowData.jl/issues/27 +# +# The currently handles data which looks like: +# 111,'STBC ',161.00,1, 0.00, 0.00,227, 1,1.09814, -8.327, 1 /* [STBC 1 ] */ +# And does _not_ handle data with a comma before the comment like: +# 111,'STBC ',161.00,1, 0.00, 0.00,227, 1,1.09814, -8.327, 1, /* [STBC 1 ] */ +@generated function parse_row!(rec::R, bytes, pos, len, options) where {R <: Buses30} + block = Expr(:block) + n = fieldcount(R) + # The last column is the comment. We need to handle the second-last column separately + # too in order to be sure we stop parsing it before hitting the comment. + for col in 1:(n - 2) + T = eltype(fieldtype(R, col)) + push!(block.args, quote + rec, pos, code = parse_value!(rec, $col, $T, bytes, pos, len, options) + end) + end + m = n - 1 + Tm = eltype(fieldtype(R, m)) + Tn = eltype(fieldtype(R, n)) + push!(block.args, quote + # @show (rec, pos, code) + # Assume no comma delim before the comment, so need to set whitespace as delim. + # And move to next non-whitespace character, so we don't immediately hit a delim. + pos = checkdelim!(bytes, pos, len, OPTIONS_SPACE) + (rec, pos, code) = parse_value!(rec, $m, $Tm, bytes, pos, len, OPTIONS_SPACE) + if newline(code) + # If we hit a newline before delimiter there is no trailing comment. + push!(getfield(rec, $n)::Vector{$Tn}, missing) + else + (rec, pos, code) = parse_value!(rec, $n, $Tn, bytes, pos, len, OPTIONS_COMMENT) + end + end) + push!(block.args, :(return rec, pos)) + # @show block + return block +end + ### ### transformers ### diff --git a/src/types.jl b/src/types.jl index ca13b85..a752314 100644 --- a/src/types.jl +++ b/src/types.jl @@ -146,6 +146,11 @@ struct Buses30 <: Buses See [`Owners`](@ref). """ owner::Vector{OwnerNum} + """ + End-of-line comments. Not part of the official PSS/E format specification, but + present in some files nonetheless. + """ + comment::Vector{Union{InlineString31,Missing}} end """ diff --git a/test/runtests.jl b/test/runtests.jl index 5fc9385..182857e 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -94,7 +94,7 @@ using Test @test startswith(repr(mime, net.caseid), "CaseID: (ic = 0, sbase = 100.0, ") @test repr(mime, net.buses; context=(:compact => true)) == "Buses with 2 records" - @test startswith(repr(mime, net.buses), "Buses with 2 records, 11 columns:\n──") + @test startswith(repr(mime, net.buses), "Buses with 2 records, 12 columns:\n──") mt_dc_line = net.multi_terminal_dc.lines[1] @test eval(Meta.parse(repr(mt_dc_line))) isa MultiTerminalDCLine @@ -497,6 +497,23 @@ using Test @test net_space.branches.j == net_space_manual.branches.j end + @testset "End-of-line comments" begin + net_eol = parse_network("testfiles/eolcomments.raw") + + # Only `Buses30` parse trailing EOL comments. + buses = net_eol.buses + @test length(buses) == 2 + @test buses.i == [10010, 337918] # first col + @test buses.owner == [1, 1] # last col before comments + @test all(contains.(buses.comment, ["NOBO 1", "NAU_E2 1"])) + + loads = net_eol.loads + @test length(loads) == 1 + @test loads.i == [10010] # first col + @test loads.owner == [1] # last non-missing col + @test isequal(loads.intrpt, [missing]) # last col + end + @testset "issues" begin sz = parse_network("testfiles/spacezero.raw") @test length(sz.buses) == 2 diff --git a/test/testfiles/eolcomments.raw b/test/testfiles/eolcomments.raw new file mode 100644 index 0000000..8def56d --- /dev/null +++ b/test/testfiles/eolcomments.raw @@ -0,0 +1,7 @@ +0,100.0,30 / PSS(tm)E-30 RAW created Thu, Oct 13 2018 13:08 + SEP 2018 V2 MODEL + MADE ON 13-Oct-2018 13:08@ + 10010,'NOBO ',161.00,1, 0.00, 0.00,327, 1,1.03182, 5.469, 1 /* [NOBO 1 ] */ +337918,'3NAUNEPPE-2+',115.00,1, 0.00, 0.00,327, 1,1.01644, 0.679, 1 /* [NAU_E2 1 ] */ +0 / end of bus cards + 10010,'T1',1,327, 1, 7.698, 4.063, 9.463, 16.549, -1.802, 2.043, 1 /* [NOBO T1 ] */