From 579480840fa907745bbbb965d9194b0b8a1fb769 Mon Sep 17 00:00:00 2001 From: Nick Robinson Date: Fri, 10 Jun 2022 17:03:11 +0100 Subject: [PATCH 1/6] WIP --- src/parsing.jl | 49 +++++++++++++++++++++++++++++++++++++++++++++---- src/types.jl | 5 +++++ 2 files changed, 50 insertions(+), 4 deletions(-) diff --git a/src/parsing.jl b/src/parsing.jl index 852fd2f..87086a1 100644 --- a/src/parsing.jl +++ b/src/parsing.jl @@ -23,6 +23,16 @@ const OPTIONS_SPACE = Parsers.Options( ignorerepeated=true, wh1=0x00, ) +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) @@ -57,7 +67,12 @@ or else it will be automatically detected when parsing the file. The delimiter can be specified with the `delim` keyword, like `delim=' '`, or else it will be automatically detected when parsing the file. """ -function parse_network(source; v::Union{Integer,Nothing}=nothing, delim::Union{Nothing,Char}=nothing) +function parse_network( + source; + v::Union{Integer,Nothing}=nothing, + delim::Union{Nothing,Char}=nothing, + comments::Bool=false, +) @debug 1 "source = $source, v = $v" bytes, pos, len = getbytes(source) d = delim === nothing ? detectdelim(bytes, pos, len) : delim @@ -79,7 +94,7 @@ function parse_network(source; v::Union{Integer,Nothing}=nothing, delim::Union{N return if is_v33 parse_network33(source, version, caseid, bytes, pos, len, options) else - parse_network30(source, version, caseid, bytes, pos, len, options) + parse_network30(source, version, caseid, bytes, pos, len, options; comments) end end @@ -126,8 +141,8 @@ function parse_network33(source, version, caseid, bytes, pos, len, options) ) end -function parse_network30(source, version, caseid, bytes, pos, len, options) - buses, pos = parse_records!(Buses30(len÷1000), bytes, pos, len, options) +function parse_network30(source, version, caseid, bytes, pos, len, options; comments) + buses, pos = parse_records!(Buses30(len÷1000), bytes, pos, len, options, comments) nbuses = length(buses) loads, pos = parse_records!(Loads(nbuses), bytes, pos, len, options) fixed_shunts = nothing @@ -244,6 +259,32 @@ end return block end +### +### Buses +### + +@generated function parse_row!(rec::R, bytes, pos, len, options) where {R <: Buses} + block = Expr(:block) + N = fieldcount(R) + 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 + Ty = eltype(fieldtype(R, N - 1)) + push!(block.args, quote + rec, pos, code = parse_value!(rec, $(N - 1), $T, bytes, pos, len, options) + end) + Tz = eltype(fieldtype(R, N)) + push!(getfield(rec, $col), missing) + push!(block.args, quote + rec, pos, code = parse_value!(rec, $N, $T, bytes, pos, len, options) + end) + # @show block + return block +end + ### ### transformers ### diff --git a/src/types.jl b/src/types.jl index ca13b85..184667c 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{String31,Missing}} end """ From 935a2436baf4e94083014d9e0bd908a3373fe8e4 Mon Sep 17 00:00:00 2001 From: Nick Robinson Date: Fri, 10 Jun 2022 17:08:30 +0100 Subject: [PATCH 2/6] WIP2 --- src/parsing.jl | 34 ++++++++++++++++++---------------- 1 file changed, 18 insertions(+), 16 deletions(-) diff --git a/src/parsing.jl b/src/parsing.jl index 87086a1..7ce3e42 100644 --- a/src/parsing.jl +++ b/src/parsing.jl @@ -190,7 +190,7 @@ end peekbyte(bytes, pos) == UInt8('\'') && !eof(bytes, pos+1, len) && peekbyte(bytes, pos+1) == UInt8('0') end -function parse_records!(rec::R, bytes, pos, len, options)::Tuple{R, Int} where {R <: Records} +function parse_records!(rec::R, bytes, pos, len, options, comments=false)::Tuple{R, Int} where {R <: Records} # Records terminated by specifying a bus number of zero or `Q`. while !( eof(bytes, pos, len) || @@ -198,7 +198,7 @@ function parse_records!(rec::R, bytes, pos, len, options)::Tuple{R, Int} where { peekbyte(bytes, pos) == UInt8(' ') && !eof(bytes, pos+1, len) && _iszero(bytes, pos+1, len) || peekbyte(bytes, pos) == UInt8('Q') ) - _, pos = parse_row!(rec, bytes, pos, len, options) + _, pos = parse_row!(rec, bytes, pos, len, options, comments) end pos = next_line(bytes, pos, len, options) # Move past a "0 bus" line. @debug 1 "Parsed $R: nrows = $(length(rec)), pos = $pos" @@ -247,7 +247,7 @@ function parse_value!(rec, col::Int, ::Type{T}, bytes, pos, len, options) where return rec, pos, code end -@generated function parse_row!(rec::R, bytes, pos, len, options) where {R <: Records} +@generated function parse_row!(rec::R, bytes, pos, len, options, _) where {R <: Records} block = Expr(:block) for col in 1:fieldcount(R) T = eltype(fieldtype(R, col)) @@ -263,7 +263,7 @@ end ### Buses ### -@generated function parse_row!(rec::R, bytes, pos, len, options) where {R <: Buses} +@generated function parse_row!(rec::R, bytes, pos, len, options, comments) where {R <: Buses} block = Expr(:block) N = fieldcount(R) for col in 1:(N - 2) @@ -273,13 +273,15 @@ end end) end Ty = eltype(fieldtype(R, N - 1)) - push!(block.args, quote - rec, pos, code = parse_value!(rec, $(N - 1), $T, bytes, pos, len, options) - end) Tz = eltype(fieldtype(R, N)) - push!(getfield(rec, $col), missing) push!(block.args, quote - rec, pos, code = parse_value!(rec, $N, $T, bytes, pos, len, options) + if comments + (rec, pos, code) = parse_value!(rec, $(N - 1), $Ty, bytes, pos, len, OPTIONS_COMMENT) + (rec, pos, code) = parse_value!(rec, $N, $Tz, bytes, pos, len, OPTIONS_COMMENT) + else + (rec, pos, code) = parse_value!(rec, $(N - 1), $Ty, bytes, pos, len, options) + push!(getfield(rec, $N), missing) + end end) # @show block return block @@ -375,7 +377,7 @@ end # - Line 5 only exists for T3 data # We determine data is T2 if there is a newline after 3 entries of line 2, else it's T3. # This means T2 data with a comment after the last entry on line 2 will fool us. -@generated function parse_row!(rec::R, bytes, pos, len, options) where {R <: Transformers} +@generated function parse_row!(rec::R, bytes, pos, len, options, _) where {R <: Transformers} block = Expr(:block) # parse row 1 append!(block.args, _parse_values(R, 1, EOL_COLS[1]-7)) @@ -419,7 +421,7 @@ const N_SPECIAL = IdDict( Branches33 => 6, # 3*2 ) -@generated function parse_row!(rec::R, bytes, pos, len, options) where {R <: Union{SwitchedShunts, ImpedanceCorrections, Branches}} +@generated function parse_row!(rec::R, bytes, pos, len, options, _) where {R <: Union{SwitchedShunts, ImpedanceCorrections, Branches}} block = Expr(:block) N = fieldcount(R) - N_SPECIAL[R] append!(block.args, _parse_values(R, 1, N)) @@ -440,7 +442,7 @@ end ### @generated function parse_row!( - rec::R, bytes, pos, len, options + rec::R, bytes, pos, len, options, _ ) where {R <: Union{Loads,Generators,MultiSectionLineGroups}} block = Expr(:block) N = fieldcount(R) - N_SPECIAL[R] @@ -457,25 +459,25 @@ end ### MultiTerminalDCLines ### -function parse_row!(rec::R, bytes, pos, len, options) where {I, R <: MultiTerminalDCLines{I}} +function parse_row!(rec::R, bytes, pos, len, options, _) where {I, R <: MultiTerminalDCLines{I}} line_id, pos = parse_idrow(I, bytes, pos, len, options) nconv = line_id.nconv converters = ACConverters(nconv) for _ in 1:nconv - converters, pos = parse_row!(converters, bytes, pos, len, options) + converters, pos = parse_row!(converters, bytes, pos, len, options, _) end ndcbs = line_id.ndcbs dc_buses = DCBuses(ndcbs) for _ in 1:ndcbs - dc_buses, pos = parse_row!(dc_buses, bytes, pos, len, options) + dc_buses, pos = parse_row!(dc_buses, bytes, pos, len, options, _) end ndcln = line_id.ndcln dc_links = DCLinks(ndcln) for _ in 1:ndcln - dc_links, pos = parse_row!(dc_links, bytes, pos, len, options) + dc_links, pos = parse_row!(dc_links, bytes, pos, len, options, _) end line = MultiTerminalDCLine(line_id, converters, dc_buses, dc_links) push!(rec.lines, line) From 2f2eb66f553e669420116f9af6f8769913c62540 Mon Sep 17 00:00:00 2001 From: Nick Robinson Date: Wed, 15 Jun 2022 12:11:29 +0100 Subject: [PATCH 3/6] WIP3 --- src/PowerFlowData.jl | 2 +- src/parsing.jl | 28 +++++++++++++++------------- src/types.jl | 2 +- test/runtests.jl | 2 +- test/testfiles/eolcomments.raw | 7 +++++++ 5 files changed, 25 insertions(+), 16 deletions(-) create mode 100644 test/testfiles/eolcomments.raw 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 7ce3e42..dbcdba8 100644 --- a/src/parsing.jl +++ b/src/parsing.jl @@ -234,7 +234,7 @@ function parse_value(::Type{T}, bytes, pos, len, options) where {T} code = res.code if invalid(code) if !(newline(code) && invaliddelimiter(code)) # not due to end-of-line comments - @warn codes(res.code) pos + # @warn codes(res.code) pos end end pos += res.tlen @@ -247,7 +247,7 @@ function parse_value!(rec, col::Int, ::Type{T}, bytes, pos, len, options) where return rec, pos, code end -@generated function parse_row!(rec::R, bytes, pos, len, options, _) where {R <: Records} +@generated function parse_row!(rec::R, bytes, pos, len, options, c) where {R <: Records} block = Expr(:block) for col in 1:fieldcount(R) T = eltype(fieldtype(R, col)) @@ -263,7 +263,7 @@ end ### Buses ### -@generated function parse_row!(rec::R, bytes, pos, len, options, comments) where {R <: Buses} +@generated function parse_row!(rec::R, bytes, pos, len, options, comments) where {R <: Buses30} block = Expr(:block) N = fieldcount(R) for col in 1:(N - 2) @@ -272,18 +272,20 @@ end rec, pos, code = parse_value!(rec, $col, $T, bytes, pos, len, options) end) end - Ty = eltype(fieldtype(R, N - 1)) + Ny = N - 1 + Ty = eltype(fieldtype(R, Ny)) Tz = eltype(fieldtype(R, N)) push!(block.args, quote if comments - (rec, pos, code) = parse_value!(rec, $(N - 1), $Ty, bytes, pos, len, OPTIONS_COMMENT) + (rec, pos, code) = parse_value!(rec, $Ny, $Ty, bytes, pos, len, OPTIONS_COMMENT) (rec, pos, code) = parse_value!(rec, $N, $Tz, bytes, pos, len, OPTIONS_COMMENT) else - (rec, pos, code) = parse_value!(rec, $(N - 1), $Ty, bytes, pos, len, options) + (rec, pos, code) = parse_value!(rec, $Ny, $Ty, bytes, pos, len, options) push!(getfield(rec, $N), missing) end end) - # @show block + push!(block.args, :(return rec, pos)) + @show block return block end @@ -377,7 +379,7 @@ end # - Line 5 only exists for T3 data # We determine data is T2 if there is a newline after 3 entries of line 2, else it's T3. # This means T2 data with a comment after the last entry on line 2 will fool us. -@generated function parse_row!(rec::R, bytes, pos, len, options, _) where {R <: Transformers} +@generated function parse_row!(rec::R, bytes, pos, len, options, c) where {R <: Transformers} block = Expr(:block) # parse row 1 append!(block.args, _parse_values(R, 1, EOL_COLS[1]-7)) @@ -421,7 +423,7 @@ const N_SPECIAL = IdDict( Branches33 => 6, # 3*2 ) -@generated function parse_row!(rec::R, bytes, pos, len, options, _) where {R <: Union{SwitchedShunts, ImpedanceCorrections, Branches}} +@generated function parse_row!(rec::R, bytes, pos, len, options, c) where {R <: Union{SwitchedShunts, ImpedanceCorrections, Branches}} block = Expr(:block) N = fieldcount(R) - N_SPECIAL[R] append!(block.args, _parse_values(R, 1, N)) @@ -459,25 +461,25 @@ end ### MultiTerminalDCLines ### -function parse_row!(rec::R, bytes, pos, len, options, _) where {I, R <: MultiTerminalDCLines{I}} +function parse_row!(rec::R, bytes, pos, len, options, c) where {I, R <: MultiTerminalDCLines{I}} line_id, pos = parse_idrow(I, bytes, pos, len, options) nconv = line_id.nconv converters = ACConverters(nconv) for _ in 1:nconv - converters, pos = parse_row!(converters, bytes, pos, len, options, _) + converters, pos = parse_row!(converters, bytes, pos, len, options, c) end ndcbs = line_id.ndcbs dc_buses = DCBuses(ndcbs) for _ in 1:ndcbs - dc_buses, pos = parse_row!(dc_buses, bytes, pos, len, options, _) + dc_buses, pos = parse_row!(dc_buses, bytes, pos, len, options, c) end ndcln = line_id.ndcln dc_links = DCLinks(ndcln) for _ in 1:ndcln - dc_links, pos = parse_row!(dc_links, bytes, pos, len, options, _) + dc_links, pos = parse_row!(dc_links, bytes, pos, len, options, c) end line = MultiTerminalDCLine(line_id, converters, dc_buses, dc_links) push!(rec.lines, line) diff --git a/src/types.jl b/src/types.jl index 184667c..a752314 100644 --- a/src/types.jl +++ b/src/types.jl @@ -150,7 +150,7 @@ struct Buses30 <: Buses End-of-line comments. Not part of the official PSS/E format specification, but present in some files nonetheless. """ - comment::Vector{Union{String31,Missing}} + comment::Vector{Union{InlineString31,Missing}} end """ diff --git a/test/runtests.jl b/test/runtests.jl index 5fc9385..72d59f7 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 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 ] */ From b471eda666fd81b977b001af66e70863bec95514 Mon Sep 17 00:00:00 2001 From: Nick Robinson Date: Thu, 14 Jul 2022 18:11:52 +0100 Subject: [PATCH 4/6] WIP don't strip comment markers --- src/parsing.jl | 70 +++++++++++++++++++----------------------------- test/runtests.jl | 16 +++++++++++ 2 files changed, 44 insertions(+), 42 deletions(-) diff --git a/src/parsing.jl b/src/parsing.jl index dbcdba8..985b9fc 100644 --- a/src/parsing.jl +++ b/src/parsing.jl @@ -23,16 +23,6 @@ const OPTIONS_SPACE = Parsers.Options( ignorerepeated=true, wh1=0x00, ) -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) @@ -67,12 +57,7 @@ or else it will be automatically detected when parsing the file. The delimiter can be specified with the `delim` keyword, like `delim=' '`, or else it will be automatically detected when parsing the file. """ -function parse_network( - source; - v::Union{Integer,Nothing}=nothing, - delim::Union{Nothing,Char}=nothing, - comments::Bool=false, -) +function parse_network(source; v::Union{Integer,Nothing}=nothing, delim::Union{Nothing,Char}=nothing) @debug 1 "source = $source, v = $v" bytes, pos, len = getbytes(source) d = delim === nothing ? detectdelim(bytes, pos, len) : delim @@ -94,7 +79,7 @@ function parse_network( return if is_v33 parse_network33(source, version, caseid, bytes, pos, len, options) else - parse_network30(source, version, caseid, bytes, pos, len, options; comments) + parse_network30(source, version, caseid, bytes, pos, len, options) end end @@ -141,8 +126,8 @@ function parse_network33(source, version, caseid, bytes, pos, len, options) ) end -function parse_network30(source, version, caseid, bytes, pos, len, options; comments) - buses, pos = parse_records!(Buses30(len÷1000), bytes, pos, len, options, comments) +function parse_network30(source, version, caseid, bytes, pos, len, options) + buses, pos = parse_records!(Buses30(len÷1000), bytes, pos, len, options) nbuses = length(buses) loads, pos = parse_records!(Loads(nbuses), bytes, pos, len, options) fixed_shunts = nothing @@ -190,7 +175,7 @@ end peekbyte(bytes, pos) == UInt8('\'') && !eof(bytes, pos+1, len) && peekbyte(bytes, pos+1) == UInt8('0') end -function parse_records!(rec::R, bytes, pos, len, options, comments=false)::Tuple{R, Int} where {R <: Records} +function parse_records!(rec::R, bytes, pos, len, options)::Tuple{R, Int} where {R <: Records} # Records terminated by specifying a bus number of zero or `Q`. while !( eof(bytes, pos, len) || @@ -198,7 +183,7 @@ function parse_records!(rec::R, bytes, pos, len, options, comments=false)::Tuple peekbyte(bytes, pos) == UInt8(' ') && !eof(bytes, pos+1, len) && _iszero(bytes, pos+1, len) || peekbyte(bytes, pos) == UInt8('Q') ) - _, pos = parse_row!(rec, bytes, pos, len, options, comments) + _, pos = parse_row!(rec, bytes, pos, len, options) end pos = next_line(bytes, pos, len, options) # Move past a "0 bus" line. @debug 1 "Parsed $R: nrows = $(length(rec)), pos = $pos" @@ -234,7 +219,7 @@ function parse_value(::Type{T}, bytes, pos, len, options) where {T} code = res.code if invalid(code) if !(newline(code) && invaliddelimiter(code)) # not due to end-of-line comments - # @warn codes(res.code) pos + @warn codes(res.code) pos end end pos += res.tlen @@ -247,7 +232,7 @@ function parse_value!(rec, col::Int, ::Type{T}, bytes, pos, len, options) where return rec, pos, code end -@generated function parse_row!(rec::R, bytes, pos, len, options, c) where {R <: Records} +@generated function parse_row!(rec::R, bytes, pos, len, options) where {R <: Records} block = Expr(:block) for col in 1:fieldcount(R) T = eltype(fieldtype(R, col)) @@ -263,29 +248,30 @@ end ### Buses ### -@generated function parse_row!(rec::R, bytes, pos, len, options, comments) where {R <: Buses30} +@generated function parse_row!(rec::R, bytes, pos, len, options) where {R <: Buses30} block = Expr(:block) - N = fieldcount(R) - for col in 1:(N - 2) + n = fieldcount(R) + 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 - Ny = N - 1 - Ty = eltype(fieldtype(R, Ny)) - Tz = eltype(fieldtype(R, N)) + m = n - 1 + Tm = eltype(fieldtype(R, m)) + Tn = eltype(fieldtype(R, n)) push!(block.args, quote - if comments - (rec, pos, code) = parse_value!(rec, $Ny, $Ty, bytes, pos, len, OPTIONS_COMMENT) - (rec, pos, code) = parse_value!(rec, $N, $Tz, bytes, pos, len, OPTIONS_COMMENT) + # @show (rec, pos, code) + pos = checkdelim!(bytes, pos, len, OPTIONS_SPACE) + (rec, pos, code) = parse_value!(rec, $m, $Tm, bytes, pos, len, OPTIONS_SPACE) + if !newline(code) + (rec, pos, code) = parse_value!(rec, $n, $Tn, bytes, pos, len, options) else - (rec, pos, code) = parse_value!(rec, $Ny, $Ty, bytes, pos, len, options) - push!(getfield(rec, $N), missing) + push!(getfield(rec, $n)::Vector{$Tn}, missing) end end) push!(block.args, :(return rec, pos)) - @show block + # @show block return block end @@ -379,7 +365,7 @@ end # - Line 5 only exists for T3 data # We determine data is T2 if there is a newline after 3 entries of line 2, else it's T3. # This means T2 data with a comment after the last entry on line 2 will fool us. -@generated function parse_row!(rec::R, bytes, pos, len, options, c) where {R <: Transformers} +@generated function parse_row!(rec::R, bytes, pos, len, options) where {R <: Transformers} block = Expr(:block) # parse row 1 append!(block.args, _parse_values(R, 1, EOL_COLS[1]-7)) @@ -423,7 +409,7 @@ const N_SPECIAL = IdDict( Branches33 => 6, # 3*2 ) -@generated function parse_row!(rec::R, bytes, pos, len, options, c) where {R <: Union{SwitchedShunts, ImpedanceCorrections, Branches}} +@generated function parse_row!(rec::R, bytes, pos, len, options) where {R <: Union{SwitchedShunts, ImpedanceCorrections, Branches}} block = Expr(:block) N = fieldcount(R) - N_SPECIAL[R] append!(block.args, _parse_values(R, 1, N)) @@ -444,7 +430,7 @@ end ### @generated function parse_row!( - rec::R, bytes, pos, len, options, _ + rec::R, bytes, pos, len, options ) where {R <: Union{Loads,Generators,MultiSectionLineGroups}} block = Expr(:block) N = fieldcount(R) - N_SPECIAL[R] @@ -461,25 +447,25 @@ end ### MultiTerminalDCLines ### -function parse_row!(rec::R, bytes, pos, len, options, c) where {I, R <: MultiTerminalDCLines{I}} +function parse_row!(rec::R, bytes, pos, len, options) where {I, R <: MultiTerminalDCLines{I}} line_id, pos = parse_idrow(I, bytes, pos, len, options) nconv = line_id.nconv converters = ACConverters(nconv) for _ in 1:nconv - converters, pos = parse_row!(converters, bytes, pos, len, options, c) + converters, pos = parse_row!(converters, bytes, pos, len, options) end ndcbs = line_id.ndcbs dc_buses = DCBuses(ndcbs) for _ in 1:ndcbs - dc_buses, pos = parse_row!(dc_buses, bytes, pos, len, options, c) + dc_buses, pos = parse_row!(dc_buses, bytes, pos, len, options) end ndcln = line_id.ndcln dc_links = DCLinks(ndcln) for _ in 1:ndcln - dc_links, pos = parse_row!(dc_links, bytes, pos, len, options, c) + dc_links, pos = parse_row!(dc_links, bytes, pos, len, options) end line = MultiTerminalDCLine(line_id, converters, dc_buses, dc_links) push!(rec.lines, line) diff --git a/test/runtests.jl b/test/runtests.jl index 72d59f7..0d7f478 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -497,6 +497,22 @@ 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") + + 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 From 83488161902d3d7479b20dbc63882b954d3888d2 Mon Sep 17 00:00:00 2001 From: Nick Robinson Date: Tue, 19 Jul 2022 13:31:40 +0100 Subject: [PATCH 5/6] Add comments --- src/parsing.jl | 20 +++++++++++++++++--- test/runtests.jl | 1 + 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/src/parsing.jl b/src/parsing.jl index 985b9fc..cdf66cf 100644 --- a/src/parsing.jl +++ b/src/parsing.jl @@ -248,9 +248,20 @@ 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 @@ -262,12 +273,15 @@ end 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) - (rec, pos, code) = parse_value!(rec, $n, $Tn, bytes, pos, len, options) - else + 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) end end) push!(block.args, :(return rec, pos)) diff --git a/test/runtests.jl b/test/runtests.jl index 0d7f478..182857e 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -500,6 +500,7 @@ using Test @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 From a1beb1c3cfd158741bc23a82ecb4b01b5a628a92 Mon Sep 17 00:00:00 2001 From: Nick Robinson Date: Sat, 22 Oct 2022 11:49:32 +0100 Subject: [PATCH 6/6] Treat inline-comment-chars as quotes to extract text only --- src/parsing.jl | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/parsing.jl b/src/parsing.jl index cdf66cf..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) @@ -281,7 +293,7 @@ end # 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) + (rec, pos, code) = parse_value!(rec, $n, $Tn, bytes, pos, len, OPTIONS_COMMENT) end end) push!(block.args, :(return rec, pos))