From 7e7c072e52a751a73dbfef48ae578d6415f49202 Mon Sep 17 00:00:00 2001 From: Christian Scaff Date: Tue, 17 Sep 2024 12:09:12 -0400 Subject: [PATCH 01/20] Created Parsers.jl to contain ACSetSpec PEG Implementation --- src/parsers.jl | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 src/parsers.jl diff --git a/src/parsers.jl b/src/parsers.jl new file mode 100644 index 0000000..e821fb1 --- /dev/null +++ b/src/parsers.jl @@ -0,0 +1,14 @@ +""" Parsers + +Test case displaying parsing of ACSetSpecs using PEG.jl. +This module also includes basic lexing rules as seen in +SyntacticModels.jl. + +""" + +module Parsers + +@reexport using PEG + + +end \ No newline at end of file From e835d2e08adecfe718692711ae7a6cd810d390e5 Mon Sep 17 00:00:00 2001 From: Christian Scaff Date: Tue, 17 Sep 2024 12:48:54 -0400 Subject: [PATCH 02/20] Preliminary PEG Grammar (Needs Testing) --- src/parsers.jl | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/src/parsers.jl b/src/parsers.jl index e821fb1..f887c00 100644 --- a/src/parsers.jl +++ b/src/parsers.jl @@ -10,5 +10,41 @@ module Parsers @reexport using PEG +# Basic Lexing rules for scanning sting of characters +# Breaks up words/structures into tokens + +@rule ws = r"\s*" +@rule eq = r"="p +@rule lparen = r"\(" +@rule rparen = r"\)" +@rule comma = r","p +@rule EOL = "\n" , ";" +@rule colon = r":"p +@rule keyWord = r"[^:{}→\n;=,\(\)]*" + +# Core Parsing rules for ACSetSpecs +# ACSetSpec Structure: +# acsetspec(head, body) +# Example: +# +# acsetspec(:(LabeledGraph{Symbol}),quote +# V(label=a) +# V(label=b) +# V(label=c) +# E(src=1,tgt=3) +# E(src=2,tgt=3) +# end +# ) +# +# This PEG.jl based parser takes from the recursive decent +# parser in ACSets.jl/ADTs.jl and parses the body in "acsetspec(head, body)" + +@rule body = r"quote" & block & r"end"p +@rule block = line[*] & r"\n?"p +@rule line = ws & statement & ws & EOL +@rule statement = keyWord & lparen & args & rparen +@rule args = (arg & comma)[*] & arg +@rule arg = args | (keyWord & eq & args) | keyWord + end \ No newline at end of file From 7e38295125296d98aec6fea1343d54ea98f5f8ea Mon Sep 17 00:00:00 2001 From: Christian Scaff Date: Tue, 17 Sep 2024 12:52:00 -0400 Subject: [PATCH 03/20] Replaced incorrect BIT OR usage with proper PEG "," --- src/parsers.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/parsers.jl b/src/parsers.jl index f887c00..c42d98b 100644 --- a/src/parsers.jl +++ b/src/parsers.jl @@ -44,7 +44,7 @@ module Parsers @rule line = ws & statement & ws & EOL @rule statement = keyWord & lparen & args & rparen @rule args = (arg & comma)[*] & arg -@rule arg = args | (keyWord & eq & args) | keyWord +@rule arg = args , (keyWord & eq & args) , keyWord end \ No newline at end of file From 468acb97a2678101d6ff079da2ead0431d4923a4 Mon Sep 17 00:00:00 2001 From: Christian Scaff Date: Wed, 18 Sep 2024 00:19:58 -0400 Subject: [PATCH 04/20] Created parser.jl test file, confirmed working project structure (imports/exports) --- src/ACSets.jl | 2 ++ src/parsers.jl | 26 +++++++++++++++++++++++--- test/parsers.jl | 13 +++++++++++++ 3 files changed, 38 insertions(+), 3 deletions(-) create mode 100644 test/parsers.jl diff --git a/src/ACSets.jl b/src/ACSets.jl index 405cc52..8526d3b 100644 --- a/src/ACSets.jl +++ b/src/ACSets.jl @@ -15,6 +15,7 @@ include("intertypes/InterTypes.jl") include("serialization/Serialization.jl") include("ADTs.jl") include("NautyInterface.jl") +include("parsers.jl") @reexport using .ColumnImplementations: AttrVar @reexport using .Schemas @@ -23,6 +24,7 @@ include("NautyInterface.jl") @reexport using .InterTypes @reexport using .ACSetSerialization using .ADTs +using .Parsers @reexport using .NautyInterface end diff --git a/src/parsers.jl b/src/parsers.jl index c42d98b..894cee5 100644 --- a/src/parsers.jl +++ b/src/parsers.jl @@ -8,8 +8,14 @@ SyntacticModels.jl. module Parsers +using Reexport @reexport using PEG +#Export lexing rules +export ws, eq, lparen, rparen, comma, EOL, colon, identifier +#Export parsing rules +export body, block, line, statement, args, arg + # Basic Lexing rules for scanning sting of characters # Breaks up words/structures into tokens @@ -20,7 +26,7 @@ module Parsers @rule comma = r","p @rule EOL = "\n" , ";" @rule colon = r":"p -@rule keyWord = r"[^:{}→\n;=,\(\)]*" +@rule identifier = r"[^:{}→\n;=,\(\)]*" # Core Parsing rules for ACSetSpecs # ACSetSpec Structure: @@ -42,9 +48,23 @@ module Parsers @rule body = r"quote" & block & r"end"p @rule block = line[*] & r"\n?"p @rule line = ws & statement & ws & EOL -@rule statement = keyWord & lparen & args & rparen +@rule statement = identifier & lparen & args & rparen @rule args = (arg & comma)[*] & arg -@rule arg = args , (keyWord & eq & args) , keyWord +@rule arg = (identifier & eq & args) , identifier + +#Deleted args rule for stack overflow problem +# @rule arg = args , (identifier & eq & args) , identifier creates stack overflow + + +# Body contains "qoute ... end", containing a block of code +# Block contains one or more lines of statements +# Line contians a statement followed by a new line or ";" +# Statement contains a call followed by arguments in parenthesis: "identifier(args)" +# args contains one or more arguments separated by commas +# arg contains multiple possibilites: +# - A list of further arguments +# - A key followed by an equals sign and a value: "identifier = args" +# - A single value end \ No newline at end of file diff --git a/test/parsers.jl b/test/parsers.jl new file mode 100644 index 0000000..5026fca --- /dev/null +++ b/test/parsers.jl @@ -0,0 +1,13 @@ +# ## Testing our parser +module ParserTests +using Test +using ACSets +using ACSets.ADTs +using ACSets.Parsers + +#Testing arg rule before applied transformation +@testset "arg" begin + @test arg("1")[1] == "1" +end + +end \ No newline at end of file From 96359f1c404d462dc0c3730ef2de9ab8caa89d58 Mon Sep 17 00:00:00 2001 From: Christian Scaff Date: Wed, 18 Sep 2024 23:10:10 -0400 Subject: [PATCH 05/20] Transformations added except @args transformation. @Arg appears to be working, yet its test are failing... --- src/parsers.jl | 35 +++++++++++++++++------------------ test/parsers.jl | 7 ++++--- 2 files changed, 21 insertions(+), 21 deletions(-) diff --git a/src/parsers.jl b/src/parsers.jl index 894cee5..582e31a 100644 --- a/src/parsers.jl +++ b/src/parsers.jl @@ -9,6 +9,8 @@ SyntacticModels.jl. module Parsers using Reexport +using ACSets, ACSets.ADTs + @reexport using PEG #Export lexing rules @@ -26,7 +28,7 @@ export body, block, line, statement, args, arg @rule comma = r","p @rule EOL = "\n" , ";" @rule colon = r":"p -@rule identifier = r"[^:{}→\n;=,\(\)]*" +@rule identifier = r"[^:{}→\n;=,\(\)\s]+" # Core Parsing rules for ACSetSpecs # ACSetSpec Structure: @@ -45,26 +47,23 @@ export body, block, line, statement, args, arg # This PEG.jl based parser takes from the recursive decent # parser in ACSets.jl/ADTs.jl and parses the body in "acsetspec(head, body)" -@rule body = r"quote" & block & r"end"p -@rule block = line[*] & r"\n?"p -@rule line = ws & statement & ws & EOL -@rule statement = identifier & lparen & args & rparen -@rule args = (arg & comma)[*] & arg -@rule arg = (identifier & eq & args) , identifier - -#Deleted args rule for stack overflow problem -# @rule arg = args , (identifier & eq & args) , identifier creates stack overflow - - - -# Body contains "qoute ... end", containing a block of code +@rule body = r"quote" & block & r"end"p |> v -> v[2] # Block contains one or more lines of statements +@rule block = line[*] & r"\n?"p |> v -> v[1] # Line contians a statement followed by a new line or ";" +@rule line = ws & statement & ws & EOL |> v -> v[2] # Statement contains a call followed by arguments in parenthesis: "identifier(args)" +@rule statement = identifier & lparen & args & rparen |> Statement(Symbol(v[1]), v[3]) # args contains one or more arguments separated by commas -# arg contains multiple possibilites: -# - A list of further arguments -# - A key followed by an equals sign and a value: "identifier = args" -# - A single value +@rule args = (arg & comma)[*] & arg |> v -> collect_args(v) +# arg can be a list of further arguments, a key-value pair, or a single value +@rule arg = (lparen & args & rparen) |> v -> v[2], + ((identifier & eq & arg) |> v -> Kwarg(Symbol(v[1]), v[3])), + ((identifier) |> v -> Value(v[1])) + +#Collects and flattens arguments into a single list +collect_args(args) = begin + return 0 #Needs to be implemented +end end \ No newline at end of file diff --git a/test/parsers.jl b/test/parsers.jl index 5026fca..44d00f6 100644 --- a/test/parsers.jl +++ b/test/parsers.jl @@ -5,9 +5,10 @@ using ACSets using ACSets.ADTs using ACSets.Parsers -#Testing arg rule before applied transformation -@testset "arg" begin - @test arg("1")[1] == "1" +@testset "args" begin + @test arg("1")[1] == Value(1) #Fails but appears to be correct + @test arg("test=2")[1] == Kwarg(:test, Value(2)) #Fails but appears to be correct + #@test arg("(1, 2, 3)") #Collection has not been implemented yet end end \ No newline at end of file From 9bdc9c7e7edb841afce3db582dcb1781d99a4b00 Mon Sep 17 00:00:00 2001 From: Christian Scaff Date: Wed, 18 Sep 2024 23:13:07 -0400 Subject: [PATCH 06/20] Minor fix to statement --- src/parsers.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/parsers.jl b/src/parsers.jl index 582e31a..d0e0521 100644 --- a/src/parsers.jl +++ b/src/parsers.jl @@ -53,7 +53,7 @@ export body, block, line, statement, args, arg # Line contians a statement followed by a new line or ";" @rule line = ws & statement & ws & EOL |> v -> v[2] # Statement contains a call followed by arguments in parenthesis: "identifier(args)" -@rule statement = identifier & lparen & args & rparen |> Statement(Symbol(v[1]), v[3]) +@rule statement = identifier & lparen & args & rparen |> v -> Statement(Symbol(v[1]), v[3]) # args contains one or more arguments separated by commas @rule args = (arg & comma)[*] & arg |> v -> collect_args(v) # arg can be a list of further arguments, a key-value pair, or a single value From 0fd29cd01ab7e6b25230d131e96a9cca7b4cd908 Mon Sep 17 00:00:00 2001 From: Christian Scaff Date: Mon, 23 Sep 2024 13:31:29 -0400 Subject: [PATCH 07/20] Rules: Arg, Args, Statement, Line, and Block now tested and working! --- Project.toml | 2 ++ src/parsers.jl | 21 ++++++++++++++++----- test/parsers.jl | 42 ++++++++++++++++++++++++++++++++++++++---- 3 files changed, 56 insertions(+), 9 deletions(-) diff --git a/Project.toml b/Project.toml index d96ab44..e99521a 100644 --- a/Project.toml +++ b/Project.toml @@ -12,11 +12,13 @@ DataStructures = "864edb3b-99cc-5e75-8d2d-829cb0a9cfe8" JSON3 = "0f8b85d8-7281-11e9-16c2-39a750bddbf1" MLStyle = "d8e11817-5142-5d16-987a-aa16d5891078" OrderedCollections = "bac558e1-5e72-5ebc-8fee-abe8a469f55d" +PEG = "12d937ae-5f68-53be-93c9-3a6f997a20a8" Permutations = "2ae35dd2-176d-5d53-8349-f30d82d94d4f" Pkg = "44cfe95a-1eb2-52ea-b672-e2afdf69b78f" PrettyTables = "08abe8d2-0d0c-5749-adfa-8a2ac140af0d" Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" Reexport = "189a3867-3050-52da-a836-e630ba90ab69" +Revise = "295af30f-e4ad-537b-8983-00126c2a3abe" SHA = "ea8e919c-243c-51af-8825-aaa63cd721ce" StaticArrays = "90137ffa-7385-5640-81b9-e52037218182" StructEquality = "6ec83bb0-ed9f-11e9-3b4c-2b04cb4e219c" diff --git a/src/parsers.jl b/src/parsers.jl index d0e0521..99ad9b4 100644 --- a/src/parsers.jl +++ b/src/parsers.jl @@ -21,7 +21,7 @@ export body, block, line, statement, args, arg # Basic Lexing rules for scanning sting of characters # Breaks up words/structures into tokens -@rule ws = r"\s*" +@rule ws = r"[^\S\r\n]*" @rule eq = r"="p @rule lparen = r"\(" @rule rparen = r"\)" @@ -57,13 +57,24 @@ export body, block, line, statement, args, arg # args contains one or more arguments separated by commas @rule args = (arg & comma)[*] & arg |> v -> collect_args(v) # arg can be a list of further arguments, a key-value pair, or a single value -@rule arg = (lparen & args & rparen) |> v -> v[2], +@rule arg = ((lparen & args & rparen) |> v -> v[2]), ((identifier & eq & arg) |> v -> Kwarg(Symbol(v[1]), v[3])), - ((identifier) |> v -> Value(v[1])) + (identifier |> v -> parse_identifier(v[1])) + # Adding another rule for checking numbers #Collects and flattens arguments into a single list -collect_args(args) = begin - return 0 #Needs to be implemented +collect_args(v::Vector{Any}) = begin + output = first.(v[1]) + push!(output, last(v)) +end + +#Parses an identifier into a number or symbol +parse_identifier(x) = begin + try + return Value(parse(Int, x)) + catch + return Value(Symbol(x)) + end end end \ No newline at end of file diff --git a/test/parsers.jl b/test/parsers.jl index 44d00f6..5c5da64 100644 --- a/test/parsers.jl +++ b/test/parsers.jl @@ -5,10 +5,44 @@ using ACSets using ACSets.ADTs using ACSets.Parsers -@testset "args" begin - @test arg("1")[1] == Value(1) #Fails but appears to be correct - @test arg("test=2")[1] == Kwarg(:test, Value(2)) #Fails but appears to be correct - #@test arg("(1, 2, 3)") #Collection has not been implemented yet +#Overaloads "==" to properly comapre two statement structs +function Base.:(==)(s1::ACSets.ADTs.Statement, s2::ACSets.ADTs.Statement) + return s1.table == s2.table && s1.element == s2.element +end + + +@testset "arg_test" begin + @test arg("1")[1] == Value(1) + @test arg("test=2")[1] == Kwarg(:test, Value(2)) + @test arg("(1,2,3)")[1] == ([Value(1), Value(2), Value(3)]) +end + +@testset "args_test" begin + @test args("a, b, c")[1] == ([Value(:a), Value(:b), Value(:c)]) + @test args("1,2,3")[1] == ([Value(1), Value(2), Value(3)]) + @test args("a=1, b=2, c=3")[1] == ([Kwarg(:a, Value(1)), Kwarg(:b, Value(2)), Kwarg(:c, Value(3))]) +end + +@testset "statement_test" begin + @test statement("test(a)")[1] == Statement(:test, [Value(:a)]) + @test statement("test(a, b)")[1] == Statement(:test, [Value(:a), Value(:b)]) + @test statement("E(src=1,tgt=3)")[1] == Statement(:E, [Kwarg(:src, Value(1)), Kwarg(:tgt, Value(3))]) + +end + + +@testset "line_test" begin + @test line("test(a)\n")[1] == Statement(:test, [Value(:a)]) + @test line("test(a);")[1] == Statement(:test, [Value(:a)]) + @test line(" test(a) \n")[1] == Statement(:test, [Value(:a)]) +end + +@testset "block_test" begin + @test block("test(a)\n test(b)\n test(c)\n")[1] == [Statement(:test, [Value(:a)]), Statement(:test, [Value(:b)]), Statement(:test, [Value(:c)])] +end + +@testset "body_test" begin + #Needs to be implemented end end \ No newline at end of file From 3070e5d7f8449b20b7028d8d29e91766125e7b77 Mon Sep 17 00:00:00 2001 From: Christian Scaff Date: Mon, 23 Sep 2024 15:48:35 -0400 Subject: [PATCH 08/20] Parsing acsetspec body argument now working! --- src/parsers.jl | 2 +- test/parsers.jl | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/parsers.jl b/src/parsers.jl index 99ad9b4..6088c9c 100644 --- a/src/parsers.jl +++ b/src/parsers.jl @@ -47,7 +47,7 @@ export body, block, line, statement, args, arg # This PEG.jl based parser takes from the recursive decent # parser in ACSets.jl/ADTs.jl and parses the body in "acsetspec(head, body)" -@rule body = r"quote" & block & r"end"p |> v -> v[2] +@rule body = r"quote\s*"p & block & r"end"p |> v -> v[2] # Block contains one or more lines of statements @rule block = line[*] & r"\n?"p |> v -> v[1] # Line contians a statement followed by a new line or ";" diff --git a/test/parsers.jl b/test/parsers.jl index 5c5da64..cb67e18 100644 --- a/test/parsers.jl +++ b/test/parsers.jl @@ -42,7 +42,7 @@ end end @testset "body_test" begin - #Needs to be implemented + @test body("quote\n test(a)\n test(b)\n test(c)\n end")[1] == [Statement(:test, [Value(:a)]), Statement(:test, [Value(:b)]), Statement(:test, [Value(:c)])] end end \ No newline at end of file From 58c56beb8771d50451a507ac93889f2bb57bd021 Mon Sep 17 00:00:00 2001 From: Christian Scaff Date: Mon, 23 Sep 2024 15:51:38 -0400 Subject: [PATCH 09/20] Added notes on further things to handle --- src/parsers.jl | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/parsers.jl b/src/parsers.jl index 6088c9c..8116e97 100644 --- a/src/parsers.jl +++ b/src/parsers.jl @@ -4,6 +4,12 @@ Test case displaying parsing of ACSetSpecs using PEG.jl. This module also includes basic lexing rules as seen in SyntacticModels.jl. +Tasks Left to Complete: + +- Add more test cases for the parser +- Call parser from ADTs.jl acsetspec function +- Add error handling for invalid input + """ module Parsers From 25002209a9700c4a904d6308625c03ad1e74c3b9 Mon Sep 17 00:00:00 2001 From: Christian Scaff Date: Mon, 23 Sep 2024 16:06:18 -0400 Subject: [PATCH 10/20] Implemented error handling function from Peg.jl's testing files. --- src/parsers.jl | 1 + test/parsers.jl | 18 ++++++++++++++++++ 2 files changed, 19 insertions(+) diff --git a/src/parsers.jl b/src/parsers.jl index 8116e97..87a3ae7 100644 --- a/src/parsers.jl +++ b/src/parsers.jl @@ -9,6 +9,7 @@ Tasks Left to Complete: - Add more test cases for the parser - Call parser from ADTs.jl acsetspec function - Add error handling for invalid input +- Enable extraction from files? """ diff --git a/test/parsers.jl b/test/parsers.jl index cb67e18..a50d62e 100644 --- a/test/parsers.jl +++ b/test/parsers.jl @@ -10,6 +10,19 @@ function Base.:(==)(s1::ACSets.ADTs.Statement, s2::ACSets.ADTs.Statement) return s1.table == s2.table && s1.element == s2.element end +#Taken from "PEG.jl/blob/master/test/misc.jl" to test parsing failure +function parse_fails_at(rule, input) + try + parse_whole(rule, input) + "parse succeeded!" + catch err + isa(err, Meta.ParseError) || rethrow() + m = match(r"^On line \d+, at column \d+ \(byte (\d+)\):", err.msg) + m == nothing && rethrow() + parse(Int, m.captures[1]) + end + end + @testset "arg_test" begin @test arg("1")[1] == Value(1) @@ -45,4 +58,9 @@ end @test body("quote\n test(a)\n test(b)\n test(c)\n end")[1] == [Statement(:test, [Value(:a)]), Statement(:test, [Value(:b)]), Statement(:test, [Value(:c)])] end +@testset "Error_Handling" begin + @test parse_fails_at(statement, "test(a, b") == 10 + #Needs a lot more testing +end + end \ No newline at end of file From 290319bbe6a8ec93faa9167d49f69828bd194792 Mon Sep 17 00:00:00 2001 From: Christian Scaff Date: Tue, 24 Sep 2024 12:52:15 -0400 Subject: [PATCH 11/20] Implemented error handling tests --- src/parsers.jl | 1 - test/parsers.jl | 58 +++++++++++++++++++++++++++++++++++++++++++------ 2 files changed, 51 insertions(+), 8 deletions(-) diff --git a/src/parsers.jl b/src/parsers.jl index 87a3ae7..028f7f8 100644 --- a/src/parsers.jl +++ b/src/parsers.jl @@ -67,7 +67,6 @@ export body, block, line, statement, args, arg @rule arg = ((lparen & args & rparen) |> v -> v[2]), ((identifier & eq & arg) |> v -> Kwarg(Symbol(v[1]), v[3])), (identifier |> v -> parse_identifier(v[1])) - # Adding another rule for checking numbers #Collects and flattens arguments into a single list collect_args(v::Vector{Any}) = begin diff --git a/test/parsers.jl b/test/parsers.jl index a50d62e..92a9ca7 100644 --- a/test/parsers.jl +++ b/test/parsers.jl @@ -23,7 +23,7 @@ function parse_fails_at(rule, input) end end - +#Test Groups per rule: @testset "arg_test" begin @test arg("1")[1] == Value(1) @test arg("test=2")[1] == Kwarg(:test, Value(2)) @@ -43,7 +43,6 @@ end end - @testset "line_test" begin @test line("test(a)\n")[1] == Statement(:test, [Value(:a)]) @test line("test(a);")[1] == Statement(:test, [Value(:a)]) @@ -54,13 +53,58 @@ end @test block("test(a)\n test(b)\n test(c)\n")[1] == [Statement(:test, [Value(:a)]), Statement(:test, [Value(:b)]), Statement(:test, [Value(:c)])] end -@testset "body_test" begin - @test body("quote\n test(a)\n test(b)\n test(c)\n end")[1] == [Statement(:test, [Value(:a)]), Statement(:test, [Value(:b)]), Statement(:test, [Value(:c)])] +@testset "full_body_test" begin + @test body("quote + V(label=a) + V(label=b) + V(label=c) + E(src=1,tgt=3) + E(src=2,tgt=3) + end")[1] == [ + Statement(:V, [Kwarg(:label, Value(:a))]) + Statement(:V, [Kwarg(:label, Value(:b))]) + Statement(:V, [Kwarg(:label, Value(:c))]) + Statement(:E, [Kwarg(:src, Value(1)), Kwarg(:tgt, Value(3))]) + Statement(:E, [Kwarg(:src, Value(2)), Kwarg(:tgt, Value(3))]) + ] +end + + +#Test Error Handling +@testset "arg_handling" begin + @test parse_fails_at(arg, "(1") == 3 #Missing "(" at index 3 + @test parse_fails_at(arg, "a=") == 3 #Missing value after "=" at index 3 + @test parse_fails_at(arg,"invalid→ident") == 8 #Invalid character at index 1 +end + +@testset "args_handling" begin + @test parse_fails_at(args, "1,") == 3 #Missing value after "," at index 3 + @test parse_fails_at(args, ", 3") == 1 #Missing initial value before "," at index 1 + @test parse_fails_at(args, "1 1") == 2 #Missing "," at index 2 +end + +#Found Issue: +# @test parse_fails_at(args, "1 , 1") == 2 #Missing "," at index 2. Should ws be allowed between commas? + +@testset "statement_handling" begin + @test parse_fails_at(statement, "test") == 5 #Missing "(" at index 5 + @test parse_fails_at(statement, "test()") == 6 #Missing argument at index 6 + @test parse_fails_at(statement, "test(1") == 7 #Missing ")" at index 7 +end + +@testset "line_handling" begin + @test parse_fails_at(line, "test(a)") == 8 #Missing EOL at index 8 + @test parse_fails_at(line, "test(a) test(b)") == 9 #Missing EOL at index 9 + @test parse_fails_at(line, ";") == 1 #Missing statement at index 1 +end + +@testset "block_handling" begin + @test parse_fails_at(block, "test(a) test(b)") == 9 #Missing EOL at index 9 end -@testset "Error_Handling" begin - @test parse_fails_at(statement, "test(a, b") == 10 - #Needs a lot more testing +@testset "body_handling" begin + @test parse_fails_at(body, "quote\ntest(a)\n") == 15 #Missing "end" at index 15 + @test parse_fails_at(body, "quote\n") == 7 #Missing "end" at index 7 end end \ No newline at end of file From 66037ec9d35085edc2d6cb49ef2199643ff93e4d Mon Sep 17 00:00:00 2001 From: Christian Scaff Date: Wed, 25 Sep 2024 18:44:44 -0400 Subject: [PATCH 12/20] Created string macro and full unit test case. --- src/parsers.jl | 41 ++++++++++++++++---------- test/parsers.jl | 76 ++++++++++++++++++++++++++++++++----------------- 2 files changed, 76 insertions(+), 41 deletions(-) diff --git a/src/parsers.jl b/src/parsers.jl index 028f7f8..7aab134 100644 --- a/src/parsers.jl +++ b/src/parsers.jl @@ -8,7 +8,7 @@ Tasks Left to Complete: - Add more test cases for the parser - Call parser from ADTs.jl acsetspec function -- Add error handling for invalid input +- Add error handling for invalid input? - Enable extraction from files? """ @@ -20,15 +20,17 @@ using ACSets, ACSets.ADTs @reexport using PEG +#Export macro +export @acsetspec_str #Export lexing rules export ws, eq, lparen, rparen, comma, EOL, colon, identifier #Export parsing rules -export body, block, line, statement, args, arg +export acset_spec, block, line, statement, args, arg # Basic Lexing rules for scanning sting of characters # Breaks up words/structures into tokens -@rule ws = r"[^\S\r\n]*" +@rule ws = r"\s*" @rule eq = r"="p @rule lparen = r"\(" @rule rparen = r"\)" @@ -42,27 +44,33 @@ export body, block, line, statement, args, arg # acsetspec(head, body) # Example: # -# acsetspec(:(LabeledGraph{Symbol}),quote -# V(label=a) -# V(label=b) -# V(label=c) -# E(src=1,tgt=3) -# E(src=2,tgt=3) -# end -# ) +# acsetspec""" +# LabeledGraph{Symbol} +# begin +# V(label=a) +# V(label=b) +# V(label=c) +# E(src=1,tgt=3) +# E(src=2,tgt=3) +# end +# """ # + # This PEG.jl based parser takes from the recursive decent -# parser in ACSets.jl/ADTs.jl and parses the body in "acsetspec(head, body)" +# parser in ACSets.jl/ADTs.jl and parses "acsetspec(head, body)" -@rule body = r"quote\s*"p & block & r"end"p |> v -> v[2] +#acset_spec takes in head and body args +@rule acset_spec = ws & head & r"begin"p & block & r"end"p |> v -> ACSetSpec(v[2], v[4]) +#Head contains ACsetSpec Type +@rule head = r"\S*"p |> v -> Symbol(v[2]) # Block contains one or more lines of statements @rule block = line[*] & r"\n?"p |> v -> v[1] # Line contians a statement followed by a new line or ";" -@rule line = ws & statement & ws & EOL |> v -> v[2] +@rule line = ws & statement & r"[^\S\r\n]*" & EOL |> v -> v[2] # Statement contains a call followed by arguments in parenthesis: "identifier(args)" @rule statement = identifier & lparen & args & rparen |> v -> Statement(Symbol(v[1]), v[3]) # args contains one or more arguments separated by commas -@rule args = (arg & comma)[*] & arg |> v -> collect_args(v) +@rule args = (arg & ws & comma)[*] & arg |> v -> collect_args(v) # arg can be a list of further arguments, a key-value pair, or a single value @rule arg = ((lparen & args & rparen) |> v -> v[2]), ((identifier & eq & arg) |> v -> Kwarg(Symbol(v[1]), v[3])), @@ -83,4 +91,7 @@ parse_identifier(x) = begin end end +#Creates a string macro to parse/create acsetspec +macro acsetspec_str(x::String) parse_whole(acset_spec, x) end + end \ No newline at end of file diff --git a/test/parsers.jl b/test/parsers.jl index 92a9ca7..8a3c349 100644 --- a/test/parsers.jl +++ b/test/parsers.jl @@ -1,10 +1,11 @@ # ## Testing our parser module ParserTests + using Test -using ACSets -using ACSets.ADTs +using ACSets, ACSets.ADTs using ACSets.Parsers + #Overaloads "==" to properly comapre two statement structs function Base.:(==)(s1::ACSets.ADTs.Statement, s2::ACSets.ADTs.Statement) return s1.table == s2.table && s1.element == s2.element @@ -23,7 +24,7 @@ function parse_fails_at(rule, input) end end -#Test Groups per rule: +# -------------- Test Groups per rule: -------------- # @testset "arg_test" begin @test arg("1")[1] == Value(1) @test arg("test=2")[1] == Kwarg(:test, Value(2)) @@ -33,6 +34,7 @@ end @testset "args_test" begin @test args("a, b, c")[1] == ([Value(:a), Value(:b), Value(:c)]) @test args("1,2,3")[1] == ([Value(1), Value(2), Value(3)]) + @test args("1 , 1")[1] == ([Value(1), Value(1)]) @test args("a=1, b=2, c=3")[1] == ([Kwarg(:a, Value(1)), Kwarg(:b, Value(2)), Kwarg(:c, Value(3))]) end @@ -53,24 +55,9 @@ end @test block("test(a)\n test(b)\n test(c)\n")[1] == [Statement(:test, [Value(:a)]), Statement(:test, [Value(:b)]), Statement(:test, [Value(:c)])] end -@testset "full_body_test" begin - @test body("quote - V(label=a) - V(label=b) - V(label=c) - E(src=1,tgt=3) - E(src=2,tgt=3) - end")[1] == [ - Statement(:V, [Kwarg(:label, Value(:a))]) - Statement(:V, [Kwarg(:label, Value(:b))]) - Statement(:V, [Kwarg(:label, Value(:c))]) - Statement(:E, [Kwarg(:src, Value(1)), Kwarg(:tgt, Value(3))]) - Statement(:E, [Kwarg(:src, Value(2)), Kwarg(:tgt, Value(3))]) - ] -end +# -------------- Test Error Handling -------------- # -#Test Error Handling @testset "arg_handling" begin @test parse_fails_at(arg, "(1") == 3 #Missing "(" at index 3 @test parse_fails_at(arg, "a=") == 3 #Missing value after "=" at index 3 @@ -80,12 +67,9 @@ end @testset "args_handling" begin @test parse_fails_at(args, "1,") == 3 #Missing value after "," at index 3 @test parse_fails_at(args, ", 3") == 1 #Missing initial value before "," at index 1 - @test parse_fails_at(args, "1 1") == 2 #Missing "," at index 2 + @test parse_fails_at(args, "1 1") == 4 #Missing "," at index 4 end -#Found Issue: -# @test parse_fails_at(args, "1 , 1") == 2 #Missing "," at index 2. Should ws be allowed between commas? - @testset "statement_handling" begin @test parse_fails_at(statement, "test") == 5 #Missing "(" at index 5 @test parse_fails_at(statement, "test()") == 6 #Missing argument at index 6 @@ -102,9 +86,49 @@ end @test parse_fails_at(block, "test(a) test(b)") == 9 #Missing EOL at index 9 end -@testset "body_handling" begin - @test parse_fails_at(body, "quote\ntest(a)\n") == 15 #Missing "end" at index 15 - @test parse_fails_at(body, "quote\n") == 7 #Missing "end" at index 7 +@testset "acset_spec_handling" begin + @test parse_fails_at(acset_spec, "begin\ntest(a)\ntest(b)\nend") == 7 + # Missing head at index 1, however, parser will try and create a head until + # it reaches a white space at index 6. This can be modified for better parsing errors. + # However, for the simplicity of the grammar, I'm keeping it as is for now. + # Right now, it registers begin as a head and fails due to the true "begin" missing. +end + +# ------------ Full Scale Tests ----------- # + +PEG.setdebug!(false) + +SchLabeledGraph = BasicSchema([:E,:V], [(:src,:E,:V),(:tgt,:E,:V)], + [:L], [(:label,:V,:L)]) + +@acset_type LabeledGraph(SchLabeledGraph, index=[:src,:tgt]) + +@testset "Constructing ACSets from Specs" begin + gspec = ACSetSpec( + :(LabeledGraph{Symbol}), + [ + Statement(:V, [Kwarg(:label, Value(:a))]) + Statement(:V, [Kwarg(:label, Value(:b))]) + Statement(:V, [Kwarg(:label, Value(:c))]) + Statement(:E, [Kwarg(:src, Value(1)), Kwarg(:tgt, Value(3))]) + Statement(:E, [Kwarg(:src, Value(2)), Kwarg(:tgt, Value(3))]) + ] + ) + g = construct(LabeledGraph{Symbol}, gspec) + @test nparts(g, :V) == 3 + @test nparts(g, :E) == 2 + + hspec = acsetspec""" + LabeledGraph{Symbol} + begin + V(label=a) + V(label=b) + V(label=c) + E(src=1,tgt=3) + E(src=2,tgt=3) + end""" + + @test construct(LabeledGraph{Symbol}, hspec) == construct(LabeledGraph{Symbol}, gspec) end end \ No newline at end of file From 3415763f4f7c4e8920f665db5ba57735e41e1ef5 Mon Sep 17 00:00:00 2001 From: Christian Scaff Date: Sun, 29 Sep 2024 10:30:05 -0400 Subject: [PATCH 13/20] A lot of issues to resolve. Pos=(0,0) giving errors. --- src/ADTs.jl | 4 ++-- src/parsers.jl | 26 +++++++++++++++++++------- test/ADTs.jl | 6 ++++++ test/parsers.jl | 31 ++++++++++++++++++++++++++++--- 4 files changed, 55 insertions(+), 12 deletions(-) diff --git a/src/ADTs.jl b/src/ADTs.jl index 477c300..15c300b 100644 --- a/src/ADTs.jl +++ b/src/ADTs.jl @@ -133,8 +133,8 @@ end expand_statement(args) = begin let ! = expand_statement @match args begin - as::AbstractVector => map(!, as) - Expr(:kw, args...) => Kwarg(args[1], !(args[2])) + as::AbstractVector => map(!, as) + Expr(:kw, args...) => Kwarg(args[1], !(args[2])); x => Value(x) x => error("hit fallthrough on $(typeof(x)), $x") end diff --git a/src/parsers.jl b/src/parsers.jl index 7aab134..153631b 100644 --- a/src/parsers.jl +++ b/src/parsers.jl @@ -61,8 +61,9 @@ export acset_spec, block, line, statement, args, arg #acset_spec takes in head and body args @rule acset_spec = ws & head & r"begin"p & block & r"end"p |> v -> ACSetSpec(v[2], v[4]) -#Head contains ACsetSpec Type -@rule head = r"\S*"p |> v -> Symbol(v[2]) +#Head contains ACsetSpec Type: As acetspec(), the PEG parser, really does not parse the head. +#It just ensure's that it exists. +@rule head = r"\S*"p |> v -> Symbol(v) # Block contains one or more lines of statements @rule block = line[*] & r"\n?"p |> v -> v[1] # Line contians a statement followed by a new line or ";" @@ -73,21 +74,32 @@ export acset_spec, block, line, statement, args, arg @rule args = (arg & ws & comma)[*] & arg |> v -> collect_args(v) # arg can be a list of further arguments, a key-value pair, or a single value @rule arg = ((lparen & args & rparen) |> v -> v[2]), - ((identifier & eq & arg) |> v -> Kwarg(Symbol(v[1]), v[3])), + ((identifier & eq & arg) |> v -> parse_assignment(v)), (identifier |> v -> parse_identifier(v[1])) #Collects and flattens arguments into a single list collect_args(v::Vector{Any}) = begin - output = first.(v[1]) + output = Vector{Args}(first.(v[1])) + #output = Vector{Union{Symbol, Value{Int64}, Value{Vector{Int64}}}}(first.(v[1])) push!(output, last(v)) end #Parses an identifier into a number or symbol -parse_identifier(x) = begin +parse_identifier(v) = begin try - return Value(parse(Int, x)) + return Value(parse(Int, v)) catch - return Value(Symbol(x)) + return Value(Symbol(v)) + end +end + +#Parses an assignment statement - Kwarg only takes type Value for args. +#Other cases than vector should have already been wrapped in "parse_identifier" +parse_assignment(v) = begin + if isa(v[3], Vector) + return Kwarg(Symbol(v[1]), Value(v[3])) + else + return Kwarg(Symbol(v[1]), v[3]) end end diff --git a/test/ADTs.jl b/test/ADTs.jl index 1caf1a0..d492e29 100644 --- a/test/ADTs.jl +++ b/test/ADTs.jl @@ -76,6 +76,11 @@ end E(3,1) T(1,2,3) end) # can't test this without CombinatorialSpaces.jl + + #Test + test_spec = acsetspec(:SemiSimplicialSet,quote V(label=:a, pos=(0,0)) end) + + end using JSON3 @@ -126,3 +131,4 @@ end end end + diff --git a/test/parsers.jl b/test/parsers.jl index 8a3c349..9e07a02 100644 --- a/test/parsers.jl +++ b/test/parsers.jl @@ -6,11 +6,17 @@ using ACSets, ACSets.ADTs using ACSets.Parsers -#Overaloads "==" to properly comapre two statement structs +#Overaloads "==" to properly compare two statement structs function Base.:(==)(s1::ACSets.ADTs.Statement, s2::ACSets.ADTs.Statement) return s1.table == s2.table && s1.element == s2.element end +#Overloads "==" to peroperly compare two keyWord arguments +# function Base.:(==)(a::Kwarg{T}, b::Kwarg{T}) where T +# return a.arg1 == b.arg1 && a.arg2 == b.arg2 +# end + + #Taken from "PEG.jl/blob/master/test/misc.jl" to test parsing failure function parse_fails_at(rule, input) try @@ -42,7 +48,10 @@ end @test statement("test(a)")[1] == Statement(:test, [Value(:a)]) @test statement("test(a, b)")[1] == Statement(:test, [Value(:a), Value(:b)]) @test statement("E(src=1,tgt=3)")[1] == Statement(:E, [Kwarg(:src, Value(1)), Kwarg(:tgt, Value(3))]) - + @test statement("A(src=(0,0), length=(1,1))")[1] == Statement(:A, [Kwarg(:src, Value([Value(0), Value(0)])), Kwarg(:length, Value([Value(1), Value(1)]))]) #Failing + @test statement("A(label=a, src=(0,0))")[1] == Statement(:A, [Kwarg(:length, Value(1)), Kwarg(:src, Value([Value(0), Value(0)]))]) + #Check Type + println("Type of Array: ", typeof([Kwarg(:length, Value(1)), Kwarg(:src, Value([Value(0), Value(0)]))])) end @testset "line_test" begin @@ -55,7 +64,6 @@ end @test block("test(a)\n test(b)\n test(c)\n")[1] == [Statement(:test, [Value(:a)]), Statement(:test, [Value(:b)]), Statement(:test, [Value(:c)])] end - # -------------- Test Error Handling -------------- # @testset "arg_handling" begin @@ -96,6 +104,7 @@ end # ------------ Full Scale Tests ----------- # +#Enable/Disable Debugging to see live parsing PEG.setdebug!(false) SchLabeledGraph = BasicSchema([:E,:V], [(:src,:E,:V),(:tgt,:E,:V)], @@ -129,6 +138,22 @@ SchLabeledGraph = BasicSchema([:E,:V], [(:src,:E,:V),(:tgt,:E,:V)], end""" @test construct(LabeledGraph{Symbol}, hspec) == construct(LabeledGraph{Symbol}, gspec) + + #Non testable without CombinatorialSpaces.jl #Failing: (0,0) registered as an expression. + # In my implementation, it's implemented as a vector. + PEG.setdebug!(false) + cspec = acsetspec""" + SemiSimplicialSet + begin + V(label=a, pos=(0,0)) + V(label=b, pos=(1,0)) + V(label=c, pos=(0,1)) + E(1,2) + E(2,3) + E(3,1) + T(1,2,3) + end""" + end end \ No newline at end of file From 99e414ea769004cea8b5e27df9e91babdb874177 Mon Sep 17 00:00:00 2001 From: Christian Scaff Date: Sun, 29 Sep 2024 23:45:21 -0400 Subject: [PATCH 14/20] Failing Try Catch Collect_args solution. Just to look at. goodnight :) --- src/parsers.jl | 31 ++++++++++++++++++++++++++++--- test/parsers.jl | 14 ++++++++++++-- 2 files changed, 40 insertions(+), 5 deletions(-) diff --git a/src/parsers.jl b/src/parsers.jl index 153631b..51732ef 100644 --- a/src/parsers.jl +++ b/src/parsers.jl @@ -78,10 +78,35 @@ export acset_spec, block, line, statement, args, arg (identifier |> v -> parse_identifier(v[1])) #Collects and flattens arguments into a single list +# If a type conversion error occurs, simply casts entire vector to closest superType collect_args(v::Vector{Any}) = begin - output = Vector{Args}(first.(v[1])) - #output = Vector{Union{Symbol, Value{Int64}, Value{Vector{Int64}}}}(first.(v[1])) - push!(output, last(v)) + try + output = first.(v[1]) + push!(output, last(v)) + catch error + if isa(error, MethodError) + #Grabs types that are failing to convert + #For some reason arg1 is the type while arg2 is the failing object + error_type_1 = error.args[1] + println("error type 1: ", error_type_1) + error_type_2 = typeof(error.args[2]) + println("error type 2: ", error_type_2) + #Checks if each type is of closest supertype + if (error_type_1 <: Kwarg && error_type_2 <: Kwarg) + output = Vector{Kwarg}(first.(v[1])) + println("kwarg casted") + elseif (error_type_1 <: Value && error_type_2 <: Value) + output = Vector{Value}(first.(v[1])) + println("value casted") + else + output = Vector{Args}(first.(v[1])) + println("arg casted") + end + push!(output, last(v)) + else + rethrow(e) + end + end end #Parses an identifier into a number or symbol diff --git a/test/parsers.jl b/test/parsers.jl index 9e07a02..52f0766 100644 --- a/test/parsers.jl +++ b/test/parsers.jl @@ -5,6 +5,7 @@ using Test using ACSets, ACSets.ADTs using ACSets.Parsers +PEG.setdebug!(false) #Overaloads "==" to properly compare two statement structs function Base.:(==)(s1::ACSets.ADTs.Statement, s2::ACSets.ADTs.Statement) @@ -49,8 +50,18 @@ end @test statement("test(a, b)")[1] == Statement(:test, [Value(:a), Value(:b)]) @test statement("E(src=1,tgt=3)")[1] == Statement(:E, [Kwarg(:src, Value(1)), Kwarg(:tgt, Value(3))]) @test statement("A(src=(0,0), length=(1,1))")[1] == Statement(:A, [Kwarg(:src, Value([Value(0), Value(0)])), Kwarg(:length, Value([Value(1), Value(1)]))]) #Failing - @test statement("A(label=a, src=(0,0))")[1] == Statement(:A, [Kwarg(:length, Value(1)), Kwarg(:src, Value([Value(0), Value(0)]))]) + @test statement("A(label=a, src=(0,0))")[1] == Statement(:A, [Kwarg(:length, Value(:a)), Kwarg(:src, Value([Value(0), Value(0)]))]) #Check Type + println("Type Parsed: ", typeof(statement("A(label=a, src=(0,0))")[1])) + println("Type of Array: ", typeof(Statement(:A, [Kwarg(:length, Value(:a)), Kwarg(:src, Value([Value(0), Value(0)]))]))) + #Further Type Checking + println("Args Types Parsed: ", typeof(statement("A(label=a, src=(0,0))")[1].element)) #Vector{ACSets.ADTs.Args} + println("Args Array: ", typeof([Kwarg(:length, Value(:a)), Kwarg(:src, Value([Value(0), Value(0)]))])) #Vector{ACSets.ADTs.Kwarg} + #Testing if we will have kwarg + value arrays in Julia: + println("Multi Type Array Check: ", typeof([Value(:a), Kwarg(:length, Value(:a))])) + #Outputs: Vector{ACSets.ADTs.Args{Symbol}} + #This implies, when we have an array of different types -> we naturally cast to supertype + println("Type of Array: ", typeof([Kwarg(:length, Value(1)), Kwarg(:src, Value([Value(0), Value(0)]))])) end @@ -141,7 +152,6 @@ SchLabeledGraph = BasicSchema([:E,:V], [(:src,:E,:V),(:tgt,:E,:V)], #Non testable without CombinatorialSpaces.jl #Failing: (0,0) registered as an expression. # In my implementation, it's implemented as a vector. - PEG.setdebug!(false) cspec = acsetspec""" SemiSimplicialSet begin From 7e27feab5a2ece9cd9a526de7cef0606e301035b Mon Sep 17 00:00:00 2001 From: Christian Scaff Date: Tue, 1 Oct 2024 23:50:41 -0400 Subject: [PATCH 15/20] Initial iteration of ACSetSpec Parser completed and passing all tests. Goodnight lovely world. --- src/parsers.jl | 92 +++++++++++++++++-------------------------------- test/parsers.jl | 53 ++++++++++++++-------------- 2 files changed, 56 insertions(+), 89 deletions(-) diff --git a/src/parsers.jl b/src/parsers.jl index 51732ef..667ddef 100644 --- a/src/parsers.jl +++ b/src/parsers.jl @@ -1,16 +1,9 @@ """ Parsers -Test case displaying parsing of ACSetSpecs using PEG.jl. -This module also includes basic lexing rules as seen in -SyntacticModels.jl. - -Tasks Left to Complete: - -- Add more test cases for the parser -- Call parser from ADTs.jl acsetspec function -- Add error handling for invalid input? -- Enable extraction from files? - +Parsing ACSetSpecs using PEG.jl. This module allows you to build custom +grammars that represent the models in strings that aren't tied to any particular +programming language syntax. Specifically functional for parsing and constructing +ACSetSpecs. """ module Parsers @@ -20,14 +13,16 @@ using ACSets, ACSets.ADTs @reexport using PEG -#Export macro +# Export macro export @acsetspec_str -#Export lexing rules + +# Export lexing rules export ws, eq, lparen, rparen, comma, EOL, colon, identifier -#Export parsing rules + +# Export parsing rules export acset_spec, block, line, statement, args, arg -# Basic Lexing rules for scanning sting of characters +# Basic Lexing rules for scanning string of characters # Breaks up words/structures into tokens @rule ws = r"\s*" @@ -42,31 +37,31 @@ export acset_spec, block, line, statement, args, arg # Core Parsing rules for ACSetSpecs # ACSetSpec Structure: # acsetspec(head, body) +# # Example: # # acsetspec""" -# LabeledGraph{Symbol} -# begin +# LabeledGraph{Symbol} +# begin # V(label=a) # V(label=b) # V(label=c) # E(src=1,tgt=3) # E(src=2,tgt=3) -# end +# end # """ # -# This PEG.jl based parser takes from the recursive decent +# This PEG.jl-based parser takes from the recursive decent # parser in ACSets.jl/ADTs.jl and parses "acsetspec(head, body)" -#acset_spec takes in head and body args +# acset_spec takes in head and body args @rule acset_spec = ws & head & r"begin"p & block & r"end"p |> v -> ACSetSpec(v[2], v[4]) -#Head contains ACsetSpec Type: As acetspec(), the PEG parser, really does not parse the head. -#It just ensure's that it exists. +# Ensures "head" exists but does not check type @rule head = r"\S*"p |> v -> Symbol(v) # Block contains one or more lines of statements @rule block = line[*] & r"\n?"p |> v -> v[1] -# Line contians a statement followed by a new line or ";" +# Line contains a statement followed by a new line or ";" @rule line = ws & statement & r"[^\S\r\n]*" & EOL |> v -> v[2] # Statement contains a call followed by arguments in parenthesis: "identifier(args)" @rule statement = identifier & lparen & args & rparen |> v -> Statement(Symbol(v[1]), v[3]) @@ -74,52 +69,27 @@ export acset_spec, block, line, statement, args, arg @rule args = (arg & ws & comma)[*] & arg |> v -> collect_args(v) # arg can be a list of further arguments, a key-value pair, or a single value @rule arg = ((lparen & args & rparen) |> v -> v[2]), - ((identifier & eq & arg) |> v -> parse_assignment(v)), + ((identifier & eq & arg) |> v -> parse_assignment(v)), (identifier |> v -> parse_identifier(v[1])) -#Collects and flattens arguments into a single list -# If a type conversion error occurs, simply casts entire vector to closest superType +# Collects and flattens arguments into a single list collect_args(v::Vector{Any}) = begin - try - output = first.(v[1]) - push!(output, last(v)) - catch error - if isa(error, MethodError) - #Grabs types that are failing to convert - #For some reason arg1 is the type while arg2 is the failing object - error_type_1 = error.args[1] - println("error type 1: ", error_type_1) - error_type_2 = typeof(error.args[2]) - println("error type 2: ", error_type_2) - #Checks if each type is of closest supertype - if (error_type_1 <: Kwarg && error_type_2 <: Kwarg) - output = Vector{Kwarg}(first.(v[1])) - println("kwarg casted") - elseif (error_type_1 <: Value && error_type_2 <: Value) - output = Vector{Value}(first.(v[1])) - println("value casted") - else - output = Vector{Args}(first.(v[1])) - println("arg casted") - end - push!(output, last(v)) - else - rethrow(e) - end - end + output = Vector{Args}(first.(v[1])) + push!(output, last(v)) end -#Parses an identifier into a number or symbol +# Parses an identifier into a symbol/integer parse_identifier(v) = begin - try - return Value(parse(Int, v)) - catch + if isnothing(tryparse(Int, string(v))) return Value(Symbol(v)) - end + else + return Value(tryparse(Int, string(v))) + end end -#Parses an assignment statement - Kwarg only takes type Value for args. -#Other cases than vector should have already been wrapped in "parse_identifier" +# Parses an assignment statement +# Vectors wrapped as Value +# Ensures singular Values are not wrapped twice parse_assignment(v) = begin if isa(v[3], Vector) return Kwarg(Symbol(v[1]), Value(v[3])) @@ -128,7 +98,7 @@ parse_assignment(v) = begin end end -#Creates a string macro to parse/create acsetspec +# Creates a string macro to parse/create acsetspec macro acsetspec_str(x::String) parse_whole(acset_spec, x) end end \ No newline at end of file diff --git a/test/parsers.jl b/test/parsers.jl index 52f0766..1403e50 100644 --- a/test/parsers.jl +++ b/test/parsers.jl @@ -1,24 +1,32 @@ -# ## Testing our parser +""" ParserTests + +This module simply contains the different tests for ensuring proper functionality +of the Parsers module. Specifically, it ensures unit tests for each PEG.jl rule functions, +unit tests for error handling of each rule, and an end-to-end test for overall functionality. +""" + module ParserTests using Test using ACSets, ACSets.ADTs using ACSets.Parsers -PEG.setdebug!(false) - #Overaloads "==" to properly compare two statement structs function Base.:(==)(s1::ACSets.ADTs.Statement, s2::ACSets.ADTs.Statement) - return s1.table == s2.table && s1.element == s2.element + s1.table == s2.table && s1.element == s2.element end -#Overloads "==" to peroperly compare two keyWord arguments -# function Base.:(==)(a::Kwarg{T}, b::Kwarg{T}) where T -# return a.arg1 == b.arg1 && a.arg2 == b.arg2 -# end +#Overloads "==" to properly compare two Kwargs +function Base.:(==)(s1::ACSets.ADTs.Kwarg, s2::ACSets.ADTs.Kwarg) + s1._1 == s2._1 && s1._2 == s2._2 +end +#Overloads "==" to properly compare two Values +function Base.:(==)(s1::ACSets.ADTs.Value, s2::ACSets.ADTs.Value) + s1._1 == s2._1 +end -#Taken from "PEG.jl/blob/master/test/misc.jl" to test parsing failure +#Taken from "PEG.jl/blob/master/test/misc.jl" to test parsing exception handling function parse_fails_at(rule, input) try parse_whole(rule, input) @@ -43,6 +51,9 @@ end @test args("1,2,3")[1] == ([Value(1), Value(2), Value(3)]) @test args("1 , 1")[1] == ([Value(1), Value(1)]) @test args("a=1, b=2, c=3")[1] == ([Kwarg(:a, Value(1)), Kwarg(:b, Value(2)), Kwarg(:c, Value(3))]) + @test args("1, b=2, c=3")[1] == ([Value(1), Kwarg(:b, Value(2)), Kwarg(:c, Value(3))]) + @test args("a=1, b=2, c=3, d=4")[1] == ([Kwarg(:a, Value(1)), Kwarg(:b, Value(2)), Kwarg(:c, Value(3)), Kwarg(:d, Value(4))]) + @test args("a=1, b=(1,1), c=2")[1] == ([Kwarg(:a, Value(1)), Kwarg(:b, Value([Value(1), Value(1)])), Kwarg(:c, Value(2))]) end @testset "statement_test" begin @@ -50,19 +61,8 @@ end @test statement("test(a, b)")[1] == Statement(:test, [Value(:a), Value(:b)]) @test statement("E(src=1,tgt=3)")[1] == Statement(:E, [Kwarg(:src, Value(1)), Kwarg(:tgt, Value(3))]) @test statement("A(src=(0,0), length=(1,1))")[1] == Statement(:A, [Kwarg(:src, Value([Value(0), Value(0)])), Kwarg(:length, Value([Value(1), Value(1)]))]) #Failing - @test statement("A(label=a, src=(0,0))")[1] == Statement(:A, [Kwarg(:length, Value(:a)), Kwarg(:src, Value([Value(0), Value(0)]))]) - #Check Type - println("Type Parsed: ", typeof(statement("A(label=a, src=(0,0))")[1])) - println("Type of Array: ", typeof(Statement(:A, [Kwarg(:length, Value(:a)), Kwarg(:src, Value([Value(0), Value(0)]))]))) - #Further Type Checking - println("Args Types Parsed: ", typeof(statement("A(label=a, src=(0,0))")[1].element)) #Vector{ACSets.ADTs.Args} - println("Args Array: ", typeof([Kwarg(:length, Value(:a)), Kwarg(:src, Value([Value(0), Value(0)]))])) #Vector{ACSets.ADTs.Kwarg} - #Testing if we will have kwarg + value arrays in Julia: - println("Multi Type Array Check: ", typeof([Value(:a), Kwarg(:length, Value(:a))])) - #Outputs: Vector{ACSets.ADTs.Args{Symbol}} - #This implies, when we have an array of different types -> we naturally cast to supertype - - println("Type of Array: ", typeof([Kwarg(:length, Value(1)), Kwarg(:src, Value([Value(0), Value(0)]))])) + @test statement("A(label=a, src=(0,0))")[1] == Statement(:A, [Kwarg(:label, Value(:a)), Kwarg(:src, Value([Value(0), Value(0)]))]) + end @testset "line_test" begin @@ -110,14 +110,12 @@ end # Missing head at index 1, however, parser will try and create a head until # it reaches a white space at index 6. This can be modified for better parsing errors. # However, for the simplicity of the grammar, I'm keeping it as is for now. - # Right now, it registers begin as a head and fails due to the true "begin" missing. + # Right now, it registers "begin" as the head and ultimately fails because "begin" is missing + # as "begin" has already been parsed in as the head. end # ------------ Full Scale Tests ----------- # -#Enable/Disable Debugging to see live parsing -PEG.setdebug!(false) - SchLabeledGraph = BasicSchema([:E,:V], [(:src,:E,:V),(:tgt,:E,:V)], [:L], [(:label,:V,:L)]) @@ -150,8 +148,7 @@ SchLabeledGraph = BasicSchema([:E,:V], [(:src,:E,:V),(:tgt,:E,:V)], @test construct(LabeledGraph{Symbol}, hspec) == construct(LabeledGraph{Symbol}, gspec) - #Non testable without CombinatorialSpaces.jl #Failing: (0,0) registered as an expression. - # In my implementation, it's implemented as a vector. + #Construct cannot be tested without CombinatorialSpaces.jl cspec = acsetspec""" SemiSimplicialSet begin From d4eded6a036486f31f390676c1c43db1eb76fe25 Mon Sep 17 00:00:00 2001 From: Christian Scaff Date: Wed, 2 Oct 2024 10:55:21 -0400 Subject: [PATCH 16/20] Removed "Revise" Package form Proect.toml --- Project.toml | 1 - 1 file changed, 1 deletion(-) diff --git a/Project.toml b/Project.toml index e99521a..8b94de5 100644 --- a/Project.toml +++ b/Project.toml @@ -18,7 +18,6 @@ Pkg = "44cfe95a-1eb2-52ea-b672-e2afdf69b78f" PrettyTables = "08abe8d2-0d0c-5749-adfa-8a2ac140af0d" Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" Reexport = "189a3867-3050-52da-a836-e630ba90ab69" -Revise = "295af30f-e4ad-537b-8983-00126c2a3abe" SHA = "ea8e919c-243c-51af-8825-aaa63cd721ce" StaticArrays = "90137ffa-7385-5640-81b9-e52037218182" StructEquality = "6ec83bb0-ed9f-11e9-3b4c-2b04cb4e219c" From ae5bbe24b702fcbe51c79a522a3132aef11562ab Mon Sep 17 00:00:00 2001 From: Christian Scaff Date: Wed, 2 Oct 2024 11:00:22 -0400 Subject: [PATCH 17/20] Reset minor accidential changes to ADTS.jl --- src/ADTs.jl | 4 ++-- test/ADTs.jl | 5 ----- 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/src/ADTs.jl b/src/ADTs.jl index 15c300b..477c300 100644 --- a/src/ADTs.jl +++ b/src/ADTs.jl @@ -133,8 +133,8 @@ end expand_statement(args) = begin let ! = expand_statement @match args begin - as::AbstractVector => map(!, as) - Expr(:kw, args...) => Kwarg(args[1], !(args[2])); + as::AbstractVector => map(!, as) + Expr(:kw, args...) => Kwarg(args[1], !(args[2])) x => Value(x) x => error("hit fallthrough on $(typeof(x)), $x") end diff --git a/test/ADTs.jl b/test/ADTs.jl index d492e29..08b67bc 100644 --- a/test/ADTs.jl +++ b/test/ADTs.jl @@ -76,11 +76,6 @@ end E(3,1) T(1,2,3) end) # can't test this without CombinatorialSpaces.jl - - #Test - test_spec = acsetspec(:SemiSimplicialSet,quote V(label=:a, pos=(0,0)) end) - - end using JSON3 From 9d35b5499543db70582f213bdc22c35fa1f27edc Mon Sep 17 00:00:00 2001 From: Christian Scaff Date: Thu, 3 Oct 2024 18:17:57 -0400 Subject: [PATCH 18/20] Modified Upon Code Review Suggestions for PR --- src/{parsers.jl => Parsers.jl} | 0 test/{parsers.jl => Parsers.jl} | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename src/{parsers.jl => Parsers.jl} (100%) rename test/{parsers.jl => Parsers.jl} (100%) diff --git a/src/parsers.jl b/src/Parsers.jl similarity index 100% rename from src/parsers.jl rename to src/Parsers.jl diff --git a/test/parsers.jl b/test/Parsers.jl similarity index 100% rename from test/parsers.jl rename to test/Parsers.jl From d554a0ebc1015c555f3528adfc283bff19b640f9 Mon Sep 17 00:00:00 2001 From: Christian Scaff Date: Thu, 3 Oct 2024 18:18:16 -0400 Subject: [PATCH 19/20] Names Changes and testset added to runtests.jl --- src/Parsers.jl | 52 +++++++------- test/Parsers.jl | 178 +++++++++++++++++++++++------------------------ test/runtests.jl | 4 ++ 3 files changed, 120 insertions(+), 114 deletions(-) diff --git a/src/Parsers.jl b/src/Parsers.jl index 667ddef..730ca41 100644 --- a/src/Parsers.jl +++ b/src/Parsers.jl @@ -41,14 +41,14 @@ export acset_spec, block, line, statement, args, arg # Example: # # acsetspec""" -# LabeledGraph{Symbol} -# begin -# V(label=a) -# V(label=b) -# V(label=c) -# E(src=1,tgt=3) -# E(src=2,tgt=3) -# end +# LabeledGraph{Symbol} +# begin +# V(label=a) +# V(label=b) +# V(label=c) +# E(src=1,tgt=3) +# E(src=2,tgt=3) +# end # """ # @@ -62,40 +62,42 @@ export acset_spec, block, line, statement, args, arg # Block contains one or more lines of statements @rule block = line[*] & r"\n?"p |> v -> v[1] # Line contains a statement followed by a new line or ";" -@rule line = ws & statement & r"[^\S\r\n]*" & EOL |> v -> v[2] +@rule line = ws & statement & r"[^\S\r\n]*" & EOL |> v -> v[2] # Statement contains a call followed by arguments in parenthesis: "identifier(args)" -@rule statement = identifier & lparen & args & rparen |> v -> Statement(Symbol(v[1]), v[3]) +@rule statement = identifier & lparen & ws & args & ws & rparen |> v -> Statement(Symbol(v[1]), v[4]) # args contains one or more arguments separated by commas @rule args = (arg & ws & comma)[*] & arg |> v -> collect_args(v) # arg can be a list of further arguments, a key-value pair, or a single value @rule arg = ((lparen & args & rparen) |> v -> v[2]), - ((identifier & eq & arg) |> v -> parse_assignment(v)), - (identifier |> v -> parse_identifier(v[1])) + ((identifier & eq & arg) |> v -> parse_assignment(v)), + (identifier |> v -> parse_identifier(v)) # Collects and flattens arguments into a single list collect_args(v::Vector{Any}) = begin - output = Vector{Args}(first.(v[1])) - push!(output, last(v)) + output = Vector{Args}(first.(v[1])) + push!(output, last(v)) end # Parses an identifier into a symbol/integer -parse_identifier(v) = begin - if isnothing(tryparse(Int, string(v))) - return Value(Symbol(v)) - else - return Value(tryparse(Int, string(v))) - end +parse_identifier(v) = begin + v_parsed = tryparse(Int, v) + + if isnothing(v_parsed) + return Value(Symbol(v)) + else + return Value(v_parsed) + end end # Parses an assignment statement # Vectors wrapped as Value # Ensures singular Values are not wrapped twice parse_assignment(v) = begin - if isa(v[3], Vector) - return Kwarg(Symbol(v[1]), Value(v[3])) - else - return Kwarg(Symbol(v[1]), v[3]) - end + if isa(v[3], Vector) + return Kwarg(Symbol(v[1]), Value(v[3])) + else + return Kwarg(Symbol(v[1]), v[3]) + end end # Creates a string macro to parse/create acsetspec diff --git a/test/Parsers.jl b/test/Parsers.jl index 1403e50..5bfc097 100644 --- a/test/Parsers.jl +++ b/test/Parsers.jl @@ -13,154 +13,154 @@ using ACSets.Parsers #Overaloads "==" to properly compare two statement structs function Base.:(==)(s1::ACSets.ADTs.Statement, s2::ACSets.ADTs.Statement) - s1.table == s2.table && s1.element == s2.element + s1.table == s2.table && s1.element == s2.element end #Overloads "==" to properly compare two Kwargs function Base.:(==)(s1::ACSets.ADTs.Kwarg, s2::ACSets.ADTs.Kwarg) - s1._1 == s2._1 && s1._2 == s2._2 + s1._1 == s2._1 && s1._2 == s2._2 end #Overloads "==" to properly compare two Values function Base.:(==)(s1::ACSets.ADTs.Value, s2::ACSets.ADTs.Value) - s1._1 == s2._1 + s1._1 == s2._1 end #Taken from "PEG.jl/blob/master/test/misc.jl" to test parsing exception handling function parse_fails_at(rule, input) - try - parse_whole(rule, input) - "parse succeeded!" - catch err - isa(err, Meta.ParseError) || rethrow() - m = match(r"^On line \d+, at column \d+ \(byte (\d+)\):", err.msg) - m == nothing && rethrow() - parse(Int, m.captures[1]) - end + try + parse_whole(rule, input) + "parse succeeded!" + catch err + isa(err, Meta.ParseError) || rethrow() + m = match(r"^On line \d+, at column \d+ \(byte (\d+)\):", err.msg) + m == nothing && rethrow() + parse(Int, m.captures[1]) end +end # -------------- Test Groups per rule: -------------- # @testset "arg_test" begin - @test arg("1")[1] == Value(1) - @test arg("test=2")[1] == Kwarg(:test, Value(2)) - @test arg("(1,2,3)")[1] == ([Value(1), Value(2), Value(3)]) + @test arg("1")[1] == Value(1) + @test arg("test=2")[1] == Kwarg(:test, Value(2)) + @test arg("(1,2,3)")[1] == ([Value(1), Value(2), Value(3)]) end @testset "args_test" begin - @test args("a, b, c")[1] == ([Value(:a), Value(:b), Value(:c)]) - @test args("1,2,3")[1] == ([Value(1), Value(2), Value(3)]) - @test args("1 , 1")[1] == ([Value(1), Value(1)]) - @test args("a=1, b=2, c=3")[1] == ([Kwarg(:a, Value(1)), Kwarg(:b, Value(2)), Kwarg(:c, Value(3))]) - @test args("1, b=2, c=3")[1] == ([Value(1), Kwarg(:b, Value(2)), Kwarg(:c, Value(3))]) - @test args("a=1, b=2, c=3, d=4")[1] == ([Kwarg(:a, Value(1)), Kwarg(:b, Value(2)), Kwarg(:c, Value(3)), Kwarg(:d, Value(4))]) - @test args("a=1, b=(1,1), c=2")[1] == ([Kwarg(:a, Value(1)), Kwarg(:b, Value([Value(1), Value(1)])), Kwarg(:c, Value(2))]) + @test args("a, b, c")[1] == ([Value(:a), Value(:b), Value(:c)]) + @test args("1,2,3")[1] == ([Value(1), Value(2), Value(3)]) + @test args("1 , 1")[1] == ([Value(1), Value(1)]) + @test args("a=1, b=2, c=3")[1] == ([Kwarg(:a, Value(1)), Kwarg(:b, Value(2)), Kwarg(:c, Value(3))]) + @test args("1, b=2, c=3")[1] == ([Value(1), Kwarg(:b, Value(2)), Kwarg(:c, Value(3))]) + @test args("a=1, b=2, c=3, d=4")[1] == ([Kwarg(:a, Value(1)), Kwarg(:b, Value(2)), Kwarg(:c, Value(3)), Kwarg(:d, Value(4))]) + @test args("a=1, b=(1,1), c=2")[1] == ([Kwarg(:a, Value(1)), Kwarg(:b, Value([Value(1), Value(1)])), Kwarg(:c, Value(2))]) + @test args("foo, b ,c")[1] == ([Value(:foo), Value(:b), Value(:c)]) end @testset "statement_test" begin - @test statement("test(a)")[1] == Statement(:test, [Value(:a)]) - @test statement("test(a, b)")[1] == Statement(:test, [Value(:a), Value(:b)]) - @test statement("E(src=1,tgt=3)")[1] == Statement(:E, [Kwarg(:src, Value(1)), Kwarg(:tgt, Value(3))]) - @test statement("A(src=(0,0), length=(1,1))")[1] == Statement(:A, [Kwarg(:src, Value([Value(0), Value(0)])), Kwarg(:length, Value([Value(1), Value(1)]))]) #Failing - @test statement("A(label=a, src=(0,0))")[1] == Statement(:A, [Kwarg(:label, Value(:a)), Kwarg(:src, Value([Value(0), Value(0)]))]) - + @test statement("test(a)")[1] == Statement(:test, [Value(:a)]) + @test statement("test(a, b)")[1] == Statement(:test, [Value(:a), Value(:b)]) + @test statement("test( foo , bar , buz )")[1] == Statement(:test, [Value(:foo), Value(:bar), Value(:buz)]) + @test statement("E(src=1,tgt=3)")[1] == Statement(:E, [Kwarg(:src, Value(1)), Kwarg(:tgt, Value(3))]) + @test statement("A(src=(0,0), length=(1,1))")[1] == Statement(:A, [Kwarg(:src, Value([Value(0), Value(0)])), Kwarg(:length, Value([Value(1), Value(1)]))]) + @test statement("A(label=a, src=(0,0))")[1] == Statement(:A, [Kwarg(:label, Value(:a)), Kwarg(:src, Value([Value(0), Value(0)]))]) end @testset "line_test" begin - @test line("test(a)\n")[1] == Statement(:test, [Value(:a)]) - @test line("test(a);")[1] == Statement(:test, [Value(:a)]) - @test line(" test(a) \n")[1] == Statement(:test, [Value(:a)]) + @test line("test(a)\n")[1] == Statement(:test, [Value(:a)]) + @test line("test(a);")[1] == Statement(:test, [Value(:a)]) + @test line(" test(a) \n")[1] == Statement(:test, [Value(:a)]) end @testset "block_test" begin - @test block("test(a)\n test(b)\n test(c)\n")[1] == [Statement(:test, [Value(:a)]), Statement(:test, [Value(:b)]), Statement(:test, [Value(:c)])] + @test block("test(a)\n test(b)\n test(c)\n")[1] == [Statement(:test, [Value(:a)]), Statement(:test, [Value(:b)]), Statement(:test, [Value(:c)])] end # -------------- Test Error Handling -------------- # @testset "arg_handling" begin - @test parse_fails_at(arg, "(1") == 3 #Missing "(" at index 3 - @test parse_fails_at(arg, "a=") == 3 #Missing value after "=" at index 3 - @test parse_fails_at(arg,"invalid→ident") == 8 #Invalid character at index 1 + @test parse_fails_at(arg, "(1") == 3 #Missing "(" at index 3 + @test parse_fails_at(arg, "a=") == 3 #Missing value after "=" at index 3 + @test parse_fails_at(arg,"invalid→ident") == 8 #Invalid character at index 1 end @testset "args_handling" begin - @test parse_fails_at(args, "1,") == 3 #Missing value after "," at index 3 - @test parse_fails_at(args, ", 3") == 1 #Missing initial value before "," at index 1 - @test parse_fails_at(args, "1 1") == 4 #Missing "," at index 4 + @test parse_fails_at(args, "1,") == 3 #Missing value after "," at index 3 + @test parse_fails_at(args, ", 3") == 1 #Missing initial value before "," at index 1 + @test parse_fails_at(args, "1 1") == 3 #Missing "," at index 3 end @testset "statement_handling" begin - @test parse_fails_at(statement, "test") == 5 #Missing "(" at index 5 - @test parse_fails_at(statement, "test()") == 6 #Missing argument at index 6 - @test parse_fails_at(statement, "test(1") == 7 #Missing ")" at index 7 + @test parse_fails_at(statement, "test") == 5 #Missing "(" at index 5 + @test parse_fails_at(statement, "test()") == 6 #Missing argument at index 6 + @test parse_fails_at(statement, "test(1") == 7 #Missing ")" at index 7 end @testset "line_handling" begin - @test parse_fails_at(line, "test(a)") == 8 #Missing EOL at index 8 - @test parse_fails_at(line, "test(a) test(b)") == 9 #Missing EOL at index 9 - @test parse_fails_at(line, ";") == 1 #Missing statement at index 1 + @test parse_fails_at(line, "test(a)") == 8 #Missing EOL at index 8 + @test parse_fails_at(line, "test(a) test(b)") == 9 #Missing EOL at index 9 + @test parse_fails_at(line, ";") == 1 #Missing statement at index 1 end @testset "block_handling" begin - @test parse_fails_at(block, "test(a) test(b)") == 9 #Missing EOL at index 9 + @test parse_fails_at(block, "test(a) test(b)") == 9 #Missing EOL at index 9 end @testset "acset_spec_handling" begin - @test parse_fails_at(acset_spec, "begin\ntest(a)\ntest(b)\nend") == 7 - # Missing head at index 1, however, parser will try and create a head until - # it reaches a white space at index 6. This can be modified for better parsing errors. - # However, for the simplicity of the grammar, I'm keeping it as is for now. - # Right now, it registers "begin" as the head and ultimately fails because "begin" is missing - # as "begin" has already been parsed in as the head. + @test parse_fails_at(acset_spec, "begin\ntest(a)\ntest(b)\nend") == 7 + # Missing head at index 1, however, parser will try and create a head until + # it reaches a white space at index 6. This can be modified for better parsing errors. + # However, for the simplicity of the grammar, I'm keeping it as is for now. + # Right now, it registers "begin" as the head and ultimately fails because "begin" is missing + # as "begin" has already been parsed in as the head. end # ------------ Full Scale Tests ----------- # SchLabeledGraph = BasicSchema([:E,:V], [(:src,:E,:V),(:tgt,:E,:V)], - [:L], [(:label,:V,:L)]) + [:L], [(:label,:V,:L)]) @acset_type LabeledGraph(SchLabeledGraph, index=[:src,:tgt]) @testset "Constructing ACSets from Specs" begin - gspec = ACSetSpec( - :(LabeledGraph{Symbol}), - [ - Statement(:V, [Kwarg(:label, Value(:a))]) - Statement(:V, [Kwarg(:label, Value(:b))]) - Statement(:V, [Kwarg(:label, Value(:c))]) - Statement(:E, [Kwarg(:src, Value(1)), Kwarg(:tgt, Value(3))]) - Statement(:E, [Kwarg(:src, Value(2)), Kwarg(:tgt, Value(3))]) - ] - ) - g = construct(LabeledGraph{Symbol}, gspec) - @test nparts(g, :V) == 3 - @test nparts(g, :E) == 2 - - hspec = acsetspec""" - LabeledGraph{Symbol} - begin - V(label=a) - V(label=b) - V(label=c) - E(src=1,tgt=3) - E(src=2,tgt=3) - end""" - - @test construct(LabeledGraph{Symbol}, hspec) == construct(LabeledGraph{Symbol}, gspec) - - #Construct cannot be tested without CombinatorialSpaces.jl - cspec = acsetspec""" - SemiSimplicialSet - begin - V(label=a, pos=(0,0)) - V(label=b, pos=(1,0)) - V(label=c, pos=(0,1)) - E(1,2) - E(2,3) - E(3,1) - T(1,2,3) + gspec = ACSetSpec( + :(LabeledGraph{Symbol}), + [ + Statement(:V, [Kwarg(:label, Value(:a))]) + Statement(:V, [Kwarg(:label, Value(:b))]) + Statement(:V, [Kwarg(:label, Value(:c))]) + Statement(:E, [Kwarg(:src, Value(1)), Kwarg(:tgt, Value(3))]) + Statement(:E, [Kwarg(:src, Value(2)), Kwarg(:tgt, Value(3))]) + ] + ) + g = construct(LabeledGraph{Symbol}, gspec) + @test nparts(g, :V) == 3 + @test nparts(g, :E) == 2 + + hspec = acsetspec""" + LabeledGraph{Symbol} + begin + V(label=a) + V(label=b) + V(label=c) + E(src=1,tgt=3) + E(src=2,tgt=3) end""" + @test construct(LabeledGraph{Symbol}, hspec) == construct(LabeledGraph{Symbol}, gspec) + + #Construct cannot be tested without CombinatorialSpaces.jl + cspec = acsetspec""" + SemiSimplicialSet + begin + V(label=a, pos=(0,0)) + V(label=b, pos=(1,0)) + V(label=c, pos=(0,1)) + E(1,2) + E(2,3) + E(3,1) + T(1,2,3) + end""" end end \ No newline at end of file diff --git a/test/runtests.jl b/test/runtests.jl index 43e45d7..c656d80 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -20,6 +20,10 @@ end include("ACSets.jl") end +@testset "Parsers" begin + include("Parsers.jl") +end + @testset "Serialization" begin include("serialization/Serialization.jl") end From aaaed945203ac3d0a68dee75ead2b25dcec71ad0 Mon Sep 17 00:00:00 2001 From: Luke Morris Date: Thu, 3 Oct 2024 18:47:11 -0400 Subject: [PATCH 20/20] Use function keyword, and link to renamed Parsers.jl --- src/ACSets.jl | 2 +- src/Parsers.jl | 23 +++++++++++------------ 2 files changed, 12 insertions(+), 13 deletions(-) diff --git a/src/ACSets.jl b/src/ACSets.jl index 8526d3b..145f8d4 100644 --- a/src/ACSets.jl +++ b/src/ACSets.jl @@ -15,7 +15,7 @@ include("intertypes/InterTypes.jl") include("serialization/Serialization.jl") include("ADTs.jl") include("NautyInterface.jl") -include("parsers.jl") +include("Parsers.jl") @reexport using .ColumnImplementations: AttrVar @reexport using .Schemas diff --git a/src/Parsers.jl b/src/Parsers.jl index 730ca41..c9d68a5 100644 --- a/src/Parsers.jl +++ b/src/Parsers.jl @@ -69,38 +69,37 @@ export acset_spec, block, line, statement, args, arg @rule args = (arg & ws & comma)[*] & arg |> v -> collect_args(v) # arg can be a list of further arguments, a key-value pair, or a single value @rule arg = ((lparen & args & rparen) |> v -> v[2]), - ((identifier & eq & arg) |> v -> parse_assignment(v)), - (identifier |> v -> parse_identifier(v)) + ((identifier & eq & arg) |> v -> parse_assignment(v)), + (identifier |> v -> parse_identifier(v)) # Collects and flattens arguments into a single list -collect_args(v::Vector{Any}) = begin +function collect_args(v::Vector{Any}) output = Vector{Args}(first.(v[1])) push!(output, last(v)) end # Parses an identifier into a symbol/integer -parse_identifier(v) = begin +function parse_identifier(v) v_parsed = tryparse(Int, v) - if isnothing(v_parsed) - return Value(Symbol(v)) + Value(Symbol(v)) else - return Value(v_parsed) + Value(v_parsed) end end # Parses an assignment statement # Vectors wrapped as Value # Ensures singular Values are not wrapped twice -parse_assignment(v) = begin - if isa(v[3], Vector) - return Kwarg(Symbol(v[1]), Value(v[3])) +function parse_assignment(v) + if v[3] isa Vector + Kwarg(Symbol(v[1]), Value(v[3])) else - return Kwarg(Symbol(v[1]), v[3]) + Kwarg(Symbol(v[1]), v[3]) end end # Creates a string macro to parse/create acsetspec macro acsetspec_str(x::String) parse_whole(acset_spec, x) end -end \ No newline at end of file +end