{-# LANGUAGE OverloadedStrings #-}-- only used in 'fromMaybe "" mbase' line of parseN3---------------------------------------------------------------------------------- See end of this file for licence information.---------------------------------------------------------------------------------- |-- Module : N3Parser-- Copyright : (c) 2003, Graham Klyne, 2009 Vasili I Galchin, 2011 Douglas Burke-- License : GPL V2---- Maintainer : Douglas Burke-- Stability : experimental-- Portability : OverloadedStrings---- This Module implements a Notation 3 parser (see [1], [2], [3]), returning a-- new 'RDFGraph' consisting of triples and namespace information parsed from-- the supplied N3 input string, or an error indication.---- REFERENCES:---- 1 <http://www.w3.org/TeamSubmission/2008/SUBM-n3-20080114/>-- Notation3 (N3): A readable RDF syntax,-- W3C Team Submission 14 January 2008---- 2 <http://www.w3.org/DesignIssues/Notation3.html>-- Tim Berners-Lee's design issues series notes and description---- 3 <http://www.w3.org/2000/10/swap/Primer.html>-- Notation 3 Primer by Sean Palmer---- NOTES:---- UTF-8 handling is not really tested.---- No performance testing has been applied.---- Not all N3 grammar elements are supported, including:---- - @\@forSome@ (we read it in but ignore the arguments)---- - @\@forAll@ (this causes a parse error)---- - formulae are lightly tested---- - string support is incomplete (e.g. unrecognized escape characters-- such as @\\q@ are probably handled incorrectly)----------------------------------------------------------------------------------moduleSwish.RDF.N3Parser(ParseResult,parseN3,parseN3fromText,parseAnyfromText,parseTextFromText,parseAltFromText,parseNameFromText-- , parsePrefixFromText,parseAbsURIrefFromText,parseLexURIrefFromText,parseURIref2FromText-- * Exports for parsers that embed Notation3 in a bigger syntax,N3Parser,N3State(..),SpecialMap,getPrefix-- a combination of the old defaultPrefix and namedPrefix productions,n3symbol-- replacement for uriRef2 -- TODO: check this is semantically correct ,quickVariable-- was varid ,lexUriRef,document,subgraph,newBlankNode)whereimportSwish.RDF.RDFGraph(RDFGraph,RDFLabel(..),ToRDFLabel(..),NamespaceMap,LookupFormula(..),addArc,setFormula,setNamespaces,emptyRDFGraph)importSwish.RDF.GraphClass(arc)importSwish.Utils.LookupMap(LookupMap(..),LookupEntryClass(..),mapFind,mapFindMaybe,mapReplaceOrAdd,mapAdd,mapReplace)importSwish.Utils.Namespace(Namespace,makeNamespace,ScopedName,getScopeNamespace,getScopedNameURI,getScopeNamespace,makeURIScopedName,makeQNameScopedName,makeNSScopedName,nullScopedName)importSwish.Utils.QName(QName)importSwish.RDF.Vocabulary(langName,rdfType,rdfFirst,rdfRest,rdfNil,owlSameAs,logImplies,xsdBoolean,xsdInteger,xsdDecimal,xsdDouble)importSwish.RDF.RDFParser(SpecialMap,ParseResult-- , mapPrefix,prefixTable,specialTable,ignore,notFollowedBy,endBy,sepEndBy,manyTill,noneOf,char,ichar,string,stringT,symbol,lexeme,whiteSpace,mkTypedLit,hex4,hex8,appendURIs)importControl.ApplicativeimportControl.Monad(forM_,foldM)importNetwork.URI(URI(..),parseURIReference)importData.Char(isSpace,isDigit,ord)importData.Maybe(fromMaybe,fromJust)importqualifiedData.TextasTimportqualifiedData.Text.LazyasLimportText.ParserCombinators.Poly.StateText------------------------------------------------------------------------ Define parser state and helper functions------------------------------------------------------------------------ | N3 parser statedataN3State=N3State{graphState::RDFGraph-- Graph under construction,thisNode::RDFLabel-- current context node (aka 'this'),prefixUris::NamespaceMap-- namespace prefix mapping table,syntaxUris::SpecialMap-- special name mapping table,nodeGen::Int-- blank node id generator,keywordsList::[T.Text]-- contents of the @keywords statement,allowLocalNames::Bool-- True if @keywords used so that bare names are QNames in default namespace}-- | Functions to update N3State vector (use with stUpdate)setPrefix::MaybeT.Text->URI->N3State->N3StatesetPrefixpreurist=st{prefixUris=p'}wherep'=mapReplaceOrAdd(makeNamespacepreuri)(prefixUrisst)-- | Set name for special syntax elementsetSName::String->ScopedName->N3State->N3StatesetSNamenamsnamst=st{syntaxUris=s'}wheres'=mapReplaceOrAdd(nam,snam)(syntaxUrisst)setSUri::String->URI->N3State->N3StatesetSUrinam=setSNamenam.makeURIScopedName-- | Set the list of tokens that can be used without needing the leading -- \@ symbol.setKeywordsList::[T.Text]->N3State->N3StatesetKeywordsListksst=st{keywordsList=ks,allowLocalNames=True}-- Functions to access state:-- | Get name for special syntax element, default nullgetSName::N3State->String->ScopedNamegetSNamestnam=mapFindnullScopedNamenam(syntaxUrisst)getSUri::N3State->String->URIgetSUristnam=getScopedNameURI$getSNamestnam-- Map prefix to URIgetPrefixURI::N3State->MaybeT.Text->MaybeURIgetPrefixURIstpre=mapFindMaybepre(prefixUrisst)getKeywordsList::N3State->[T.Text]getKeywordsList=keywordsListgetAllowLocalNames::N3State->BoolgetAllowLocalNames=allowLocalNames-- Return function to update graph in N3 parser state,-- using the supplied function of a graph--updateGraph::(RDFGraph->RDFGraph)->N3State->N3StateupdateGraphfs=s{graphState=f(graphStates)}------------------------------------------------------------------------ Define top-level parser function:-- accepts a string and returns a graph or error----------------------------------------------------------------------typeN3Parsera=ParserN3Statea-- | Parse a string as N3 (with no real base URI).-- -- See 'parseN3' if you need to provide a base URI.--parseN3fromText::L.Text-- ^ input in N3 format.->ParseResultparseN3fromText=flipparseN3Nothing-- | Parse a string with an optional base URI.-- -- See also 'parseN3fromString'. --parseN3::L.Text-- ^ input in N3 format.->MaybeQName-- ^ optional base URI->ParseResultparseN3txtmbase=parseAnyfromTextdocumentmbasetxt{-
-- useful for testing
test :: String -> RDFGraph
test = either error id . parseAnyfromString document Nothing
-}hashURI::URIhashURI=fromJust$parseURIReference"#"-- TODO: change from QName to URI for the base?-- | Function to supply initial context and parse supplied term.--parseAnyfromText::N3Parsera-- ^ parser to apply->MaybeQName-- ^ base URI of the input, or @Nothing@ to use default base value->L.Text-- ^ input to be parsed->EitherStringaparseAnyfromTextparsermbaseinput=letpmap=LookupMap[makeNamespaceNothinghashURI]muri=fmap(makeQNameScopedNameNothing)mbasesmap=LookupMap$specialTablemuripstate=N3State{graphState=emptyRDFGraph,thisNode=NoNode,prefixUris=pmap,syntaxUris=smap,nodeGen=0,keywordsList=["a","is","of","true","false"]-- not 100% sure about true/false here,allowLocalNames=False}(result,_,_)=runParserparserpstateinputinresultnewBlankNode::N3ParserRDFLabelnewBlankNode=don<-stQuery(succ.nodeGen)stUpdate$\s->s{nodeGen=n}return$Blank(shown)-- Test functions for selected element parsing-- TODO: remove theseparseTextFromText::String->L.Text->EitherStringStringparseTextFromTexts=parseAnyfromText(strings)NothingparseAltFromText::String->String->L.Text->EitherStringStringparseAltFromTexts1s2=parseAnyfromText(strings1<|>strings2)NothingparseNameFromText::L.Text->EitherStringStringparseNameFromText=parseAnyfromTextn3NameStrNothing{-
This has been made tricky by the attempt to remove the default list
of prefixes from the starting point of a N3 parse and the subsequent
attempt to add every new namespace we come across to the parser state.
So we add in the original default namespaces for testing, since
this routine is really for testing.
-}addTestPrefixes::N3Parser()addTestPrefixes=stUpdate$\st->st{prefixUris=LookupMapprefixTable}-- should append to existing map{-
parsePrefixFromText :: L.Text -> Either String URI
parsePrefixFromText =
parseAnyfromText p Nothing
where
p = do
addTestPrefixes
pref <- n3Name
st <- stGet
case getPrefixURI st (Just pref) of
Just uri -> return uri
_ -> fail $ "Undefined prefix: '" ++ pref ++ "'"
-}parseAbsURIrefFromText::L.Text->EitherStringURIparseAbsURIrefFromText=parseAnyfromTextexplicitURINothingparseLexURIrefFromText::L.Text->EitherStringURIparseLexURIrefFromText=parseAnyfromTextlexUriRefNothingparseURIref2FromText::L.Text->EitherStringScopedNameparseURIref2FromText=parseAnyfromText(addTestPrefixes*>n3symbol)Nothing------------------------------------------------------------------------ Syntax productions------------------------------------------------------------------------ helper routinescomma,semiColon,fullStop::N3Parser()comma=ignore$symbol","semiColon=ignore$symbol";"fullStop=ignore$symbol"."-- a specialization of bracket/between br::String->String->N3Parsera->N3Parserabrlsymrsym=bracket(symbollsym)(symbolrsym)-- to make porting from parsec to polyparse easierbetween::Parserslbr->Parsersrbr->Parsersa->Parsersabetween=bracket-- The @ character is optional if the keyword is in the-- keyword list--atSign::T.Text->N3Parser()atSigns=dost<-stGetletp=ichar'@'ifs`elem`getKeywordsListstthenignore$optionalpelsepatWord::T.Text->N3ParserT.TextatWords=doatSigns-- TODO: does it really make sense to add the not-followed-by-a-colon rule here?-- apply to both cases even though should only really be necessary-- when the at sign is not given--lexeme$stringTs*>notFollowedBy(==':')returns{-
Since operatorLabel can be used to add a label with an
unknown namespace, we need to ensure that the namespace
is added if not known. If the namespace prefix is already
in use then it is over-written (rather than add a new
prefix for the label).
TODO:
- could we use the reverse lookupmap functionality to
find if the given namespace URI is in the namespace
list? If it is, use it's key otherwise do a
mapReplaceOrAdd for the input namespace.
-}operatorLabel::ScopedName->N3ParserRDFLabeloperatorLabelsnam=dost<-stGetletsns=getScopeNamespacesnamopmap=prefixUrisstpkey=entryKeysnspval=entryValsnsrval=Ressnam-- the lookup and the replacement could be fusedcasemapFindMaybepkeyopmapofJustval|val==pval->returnrval|otherwise->dostUpdate$\s->s{prefixUris=mapReplaceopmapsns}returnrval_->dostUpdate$\s->s{prefixUris=mapAddopmapsns}returnrval{-
Add statement to graph in N3 parser state.
To support literals that are written directly/implicitly - i.e. as
true/false/1/1.0/1.0e23 - rather than a string with an explicit
datatype we need to special case handling of the object label for
literals. Is this actually needed? The N3 Formatter now doesn't
display the xsd: datatypes on output, but there may be issues with
other formats (e.g RDF/XML once it is supported).
-}typeAddStatement=RDFLabel->N3Parser()addStatement::RDFLabel->RDFLabel->AddStatementaddStatementspo@(Lit_(Justdtype))|dtype`elem`[xsdBoolean,xsdInteger,xsdDecimal,xsdDouble]=doost<-stGetletstmt=arcspooldp=prefixUrisostogs=graphStateostnewp=mapReplaceOrAdd(getScopeNamespacedtype)oldpstUpdate$\st->st{prefixUris=newp,graphState=addArcstmtogs}addStatementspo=stUpdate(updateGraph(addArc(arcspo)))addStatementRev::RDFLabel->RDFLabel->AddStatementaddStatementRevops=addStatementspo{-
A number of productions require a name, which starts with
[A-Z_a-z#x00c0-#x00d6#x00d8-#x00f6#x00f8-#x02ff#x0370-#x037d#x037f-#x1fff#x200c-#x200d#x2070-#x218f#x2c00-#x2fef#x3001-#xd7ff#xf900-#xfdcf#xfdf0-#xfffd#x00010000-#x000effff]
and then has
[\-0-9A-Z_a-z#x00b7#x00c0-#x00d6#x00d8-#x00f6#x00f8-#x037d#x037f-#x1fff#x200c-#x200d#x203f-#x2040#x2070-#x218f#x2c00-#x2fef#x3001-#xd7ff#xf900-#xfdcf#xfdf0-#xfffd#x00010000-#x000effff]*
we encode this as the n3Name production
-}isaz,is09,isaz09::Char->Boolisazc=c>='a'&&c<='z'is09c=c>='0'&&c<='9'isaz09c=isazc||is09cmatch::(Orda)=>a->[(a,a)]->Boolmatchv=any(\(l,h)->v>=l&&v<=h)startChar::Char->BoolstartCharc=leti=ordcinc=='_'||matchc[('A','Z'),('a','z')]||matchi[(0x00c0,0x00d6),(0x00d8,0x00f6),(0x00f8,0x02ff),(0x0370,0x037d),(0x037f,0x1fff),(0x200c,0x200d),(0x2070,0x218f),(0x2c00,0x2fef),(0x3001,0xd7ff),(0xf900,0xfdcf),(0xfdf0,0xfffd),(0x00010000,0x000effff)]inBody::Char->BoolinBodyc=leti=ordcinc`elem`"-_"||i==0x007||matchc[('0','9'),('A','Z'),('a','z')]||matchi[(0x00c0,0x00d6),(0x00d8,0x00f6),(0x00f8,0x037d),(0x037f,0x1fff),(0x200c,0x200d),(0x203f,0x2040),(0x2070,0x218f),(0x2c00,0x2fef),(0x3001,0xd7ff),(0xf900,0xfdcf),(0xfdf0,0xfffd),(0x00010000,0x000effff)]-- should this be strict or lazy text?n3Name::N3ParserT.Textn3Name=T.cons<$>n3Init<*>n3Bodywheren3Init=satisfystartCharn3Body=L.toStrict<$>manySatisfyinBodyn3NameStr::N3ParserStringn3NameStr=T.unpack<$>n3Name{-
quickvariable ::= \?[A-Z_a-z#x00c0-#x00d6#x00d8-#x00f6#x00f8-#x02ff#x0370-#x037d#x037f-#x1fff#x200c-#x200d#x2070-#x218f#x2c00-#x2fef#x3001-#xd7ff#xf900-#xfdcf#xfdf0-#xfffd#x00010000-#x000effff][\-0-9A-Z_a-z#x00b7#x00c0-#x00d6#x00d8-#x00f6#x00f8-#x037d#x037f-#x1fff#x200c-#x200d#x203f-#x2040#x2070-#x218f#x2c00-#x2fef#x3001-#xd7ff#xf900-#xfdcf#xfdf0-#xfffd#x00010000-#x000effff]*
-}-- TODO: is mapping to Var correct?quickVariable::N3ParserRDFLabelquickVariable=char'?'*>(Var<$>n3NameStr){-
string ::= ("""[^"\\]*(?:(?:\\.|"(?!""))[^"\\]*)*""")|("[^"\\]*(?:\\.[^"\\]*)*")
or
string ::= tripleQuoted | singleQUoted
-}n3string::N3ParserT.Textn3string=tripleQuoted<|>singleQuoted{-
singleQuoted ::= "[^"\\]*(?:\\.[^"\\]*)*"
asciiChars :: String
asciiChars = map chr [0x20..0x7e]
asciiCharsN3 :: String
asciiCharsN3 = filter (`notElem` "\\\"") asciiChars
-}digit::N3ParserChardigit=satisfyisDigit{-
This is very similar to NTriples accept that also allow the escaping of '
even though it is not required.
The Python rules allow \N{name}, where name is the Unicode name. It's
not clear whether we need to support this too, so for now we do not.
-}protectedChar::N3ParserCharprotectedChar=(char't'*>return'\t')<|>(char'n'*>return'\n')<|>(char'r'*>return'\r')<|>(char'"'*>return'"')<|>(char'\''*>return'\'')<|>(char'\\'*>return'\\')<|>(char'u'*>hex4)<|>(char'U'*>hex8)-- Accept an escape character or any character as long as it isn't-- a new-line or quote. Unrecognized escape sequences should therefore-- be left alone by this. --n3Character::N3ParserCharn3Character=(char'\\'*>(protectedChar<|>return'\\'))<|>noneOf"\"\n"{-
<|> (oneOf asciiCharsN3 <?> "ASCII character")
-- TODO: bodyChar and asciiCharsN3 overlap
<|> (oneOf bodyChar <?> "Unicode character")
-}sQuot::N3ParserCharsQuot=char'"'{-
TODO: there must be a better way of building up the Text
-}singleQuoted::N3ParserT.TextsingleQuoted=fmapT.pack(bracketsQuotsQuot$manyn3Character){-
tripleQUoted ::= """[^"\\]*(?:(?:\\.|"(?!""))[^"\\]*)*"""
-}tripleQuoted::N3ParserT.TexttripleQuoted=tQuot*>fmapT.pack(manyTill(n3Character<|>sQuot<|>char'\n')tQuot)where-- tQuot = try (count 3 sQuot)tQuot=exactly3sQuotgetDefaultPrefix::N3ParserNamespacegetDefaultPrefix=dos<-stGetcasegetPrefixURIsNothingofJusturi->return$makeNamespaceNothinguri_->fail"No default prefix defined; how unexpected!"addBase::URI->N3Parser()addBase=stUpdate.setSUri"base"addPrefix::MaybeT.Text->URI->N3Parser()addPrefixp=stUpdate.setPrefixp{-|
Update the set of keywords that can be given without
an \@ sign.
-}updateKeywordsList::[T.Text]->N3Parser()updateKeywordsList=stUpdate.setKeywordsList{-
document ::= | statements_optional EOF
-}document::N3ParserRDFGraphdocument=mkGr<$>(whiteSpace*>statementsOptional*>eof*>stGet)wheremkGrs=setNamespaces(prefixUriss)(graphStates){-
statements_optional ::= | statement "." statements_optional
| void
-}statementsOptional::N3Parser()statementsOptional=ignore$endBy(lexemestatement)fullStop{-
statement ::= | declaration
| existential
| simpleStatement
| universal
-}statement::N3Parser()statement=declaration<|>existential<|>universal<|>simpleStatement-- having an error here leads to less informative errors in general, it seems-- <?> "statement (existential or universal quantification or a simple statement)"{-
declaration ::= | "@base" explicituri
| "@keywords" barename_csl
| "@prefix" prefix explicituri
-}-- TODO: do we need the try statements here? atWord would need to have a try on '@'-- (if applicable) which should mean being able to get rid of try--declaration::N3Parser()declaration=oneOf[atWord"base">>explicitURI>>=addBase,atWord"keywords">>bareNameCsl>>=updateKeywordsList,atWord"prefix"*>getPrefix]{-
(try (atWord "base") >> explicitURI >>= addBase)
<|>
(try (atWord "keywords") >> bareNameCsl >>= updateKeywordsList)
<|>
(try (atWord "prefix") *> getPrefix)
-}getPrefix::N3Parser()getPrefix=dop<-lexemeprefixu<-explicitURIaddPrefixpu{-
explicituri ::= <[^>]*>
Note: white space is to be ignored within <>
-}explicitURI::N3ParserURIexplicitURI=doletlb=char'<'rb=char'>'-- TODO: do the whitespace definitions match?ustr<-betweenlbrb$many(satisfy(/='>'))letuclean=filter(not.isSpace)ustrcaseparseURIReferenceucleanofNothing->fail$"Unable to convert <"++uclean++"> to a URI"Justuref->dos<-stGetletbase=getSUris"base"eitherfailreturn$appendURIsbaseuref-- production from the old parser; used in SwishScriptlexUriRef::N3ParserURIlexUriRef=lexemeexplicitURI{-
barename ::= [A-Z_a-z#x00c0-#x00d6#x00d8-#x00f6#x00f8-#x02ff#x0370-#x037d#x037f-#x1fff#x200c-#x200d#x2070-#x218f#x2c00-#x2fef#x3001-#xd7ff#xf900-#xfdcf#xfdf0-#xfffd#x00010000-#x000effff][\-0-9A-Z_a-z#x00b7#x00c0-#x00d6#x00d8-#x00f6#x00f8-#x037d#x037f-#x1fff#x200c-#x200d#x203f-#x2040#x2070-#x218f#x2c00-#x2fef#x3001-#xd7ff#xf900-#xfdcf#xfdf0-#xfffd#x00010000-#x000effff]*
barename_csl ::= | barename barename_csl_tail
| void
barename_csl_tail ::= | "," barename barename_csl_tail
| void
-}bareNameCsl::N3Parser[T.Text]bareNameCsl=sepBy(lexemebareName)commabareName::N3ParserT.TextbareName=n3Name{-
prefix ::= ([A-Z_a-z#x00c0-#x00d6#x00d8-#x00f6#x00f8-#x02ff#x0370-#x037d#x037f-#x1fff#x200c-#x200d#x2070-#x218f#x2c00-#x2fef#x3001-#xd7ff#xf900-#xfdcf#xfdf0-#xfffd#x00010000-#x000effff][\-0-9A-Z_a-z#x00b7#x00c0-#x00d6#x00d8-#x00f6#x00f8-#x037d#x037f-#x1fff#x200c-#x200d#x203f-#x2040#x2070-#x218f#x2c00-#x2fef#x3001-#xd7ff#xf900-#xfdcf#xfdf0-#xfffd#x00010000-#x000effff]*)?:
-}prefix::N3Parser(MaybeT.Text)prefix=optional(lexemen3Name)<*char':'{-
symbol ::= | explicituri
| qname
symbol_csl ::= | symbol symbol_csl_tail
| void
symbol_csl_tail ::= | "," symbol symbol_csl_tail
| void
-}n3symbol::N3ParserScopedNamen3symbol=(makeURIScopedName<$>explicitURI)<|>qnamesymbolCsl::N3Parser[ScopedName]symbolCsl=sepBy(lexemen3symbol)comma{-
qname ::= (([A-Z_a-z#x00c0-#x00d6#x00d8-#x00f6#x00f8-#x02ff#x0370-#x037d#x037f-#x1fff#x200c-#x200d#x2070-#x218f#x2c00-#x2fef#x3001-#xd7ff#xf900-#xfdcf#xfdf0-#xfffd#x00010000-#x000effff][\-0-9A-Z_a-z#x00b7#x00c0-#x00d6#x00d8-#x00f6#x00f8-#x037d#x037f-#x1fff#x200c-#x200d#x203f-#x2040#x2070-#x218f#x2c00-#x2fef#x3001-#xd7ff#xf900-#xfdcf#xfdf0-#xfffd#x00010000-#x000effff]*)?:)?[A-Z_a-z#x00c0-#x00d6#x00d8-#x00f6#x00f8-#x02ff#x0370-#x037d#x037f-#x1fff#x200c-#x200d#x2070-#x218f#x2c00-#x2fef#x3001-#xd7ff#xf900-#xfdcf#xfdf0-#xfffd#x00010000-#x000effff][\-0-9A-Z_a-z#x00b7#x00c0-#x00d6#x00d8-#x00f6#x00f8-#x037d#x037f-#x1fff#x200c-#x200d#x203f-#x2040#x2070-#x218f#x2c00-#x2fef#x3001-#xd7ff#xf900-#xfdcf#xfdf0-#xfffd#x00010000-#x000effff]*
TODO:
Note that, for now, we explicitly handle blank nodes
(of the form _:name) direcly in pathItem'.
This is not a good idea since qname' is used elsewhere
and so shouldn't we do the same thing there too?
-}qname::N3ParserScopedNameqname=fmap(uncurrymakeNSScopedName)(char':'>>g)<|>(n3Name>>=fullOrLocalQName)whereg=(,)<$>getDefaultPrefix<*>(n3Name<|>return"")fullOrLocalQName::T.Text->N3ParserScopedNamefullOrLocalQNamename=(char':'*>fullQNamename)<|>localQNamenamefullQName::T.Text->N3ParserScopedNamefullQNamename=makeNSScopedName<$>findPrefixname<*>(n3Name<|>pure"")findPrefix::T.Text->N3ParserNamespacefindPrefixpre=dost<-stGetcasemapFindMaybe(Justpre)(prefixUrisst)ofJusturi->return$makeNamespace(Justpre)uriNothing->failBad$"Prefix '"++T.unpackpre++":' not bound."localQName::T.Text->N3ParserScopedNamelocalQNamename=dost<-stGetifgetAllowLocalNamesstthenletg=(,)<$>getDefaultPrefix<*>purenameinuncurrymakeNSScopedName<$>gelsefail("Invalid 'bare' word: "++T.unpackname)-- TODO: not ideal error message; can we handle this case differently?{-
existential ::= | "@forSome" symbol_csl
For now we just read in the symbols and ignore them,
since we do not mark blank nodes as existentially quantified
(we assume this is the case).
TODO: fix this?
-}existential::N3Parser()-- existential = try (atWord "forSome") *> symbolCsl >> return ()existential=atWord"forSome"*>symbolCsl*>pure(){-
simpleStatement ::= | subject propertylist
-}simpleStatement::N3Parser()simpleStatement=subject>>=propertyListWith{-
subject ::= | expression
-}subject::N3ParserRDFLabelsubject=lexemeexpression{-
expression ::= | pathitem pathtail
pathtail ::= | "!" expression
| "^" expression
| void
-}expression::N3ParserRDFLabelexpression=doi<-pathItemletbackwardExpr=char'!'*>returnaddStatementRevforwardExpr=char'^'*>returnaddStatementmpt<-optional((,)<$>lexeme(forwardExpr<|>backwardExpr)<*>lexemeexpression)casemptofNothing->returniJust(addFunc,pt)->dobNode<-newBlankNodeaddFuncbNodeptireturnbNode{-
pathitem ::= | "(" pathlist ")"
| "[" propertylist "]"
| "{" formulacontent "}"
| boolean
| literal
| numericliteral
| quickvariable
| symbol
pathlist ::= | expression pathlist
| void
Need to think about how to handle formulae, since need to know the context
of the call to know where to add them.
TOOD: may include direct support for blank nodes here,
namely convert _:stringval -> Blank stringval since although
this should be done by symbol the types don't seem to easily match
up (at first blush anyway)
-}pathItem::N3ParserRDFLabelpathItem=br"("")"pathList<|>br"[""]"propertyListBNode<|>br"{""}"formulaContent-- <|> try boolean<|>boolean<|>literal<|>numericLiteral<|>quickVariable<|>Blank<$>(string"_:"*>n3NameStr)-- TODO a hack that needs fixing<|>Res<$>n3symbol{-
we create a blank node for the list and return it, whilst
adding the list contents to the graph
-}pathList::N3ParserRDFLabelpathList=docts<-many(lexemeexpression)eNode<-operatorLabelrdfNilcasectsof[]->returneNode(c:cs)->dosNode<-newBlankNodefirst<-operatorLabelrdfFirstaddStatementsNodefirstclNode<-foldMaddElemsNodecsrest<-operatorLabelrdfRestaddStatementlNoderesteNodereturnsNodewhereaddElemprevNodecurElem=dobNode<-newBlankNodefirst<-operatorLabelrdfFirstrest<-operatorLabelrdfRestaddStatementprevNoderestbNodeaddStatementbNodefirstcurElemreturnbNode{-
formulacontent ::= | statementlist
statementlist ::= | statement statementtail
| void
statementtail ::= | "." statementlist
| void
-}restoreState::N3State->N3ParserN3StaterestoreStateorigState=dooldState<-stGetstUpdate$\_->origState{nodeGen=nodeGenoldState}returnoldState{-
We create a subgraph and assign it to a blank node, returning the
blank node. At present it is a combination of the subgraph and formula
productions from the origial parser.
TODO: is it correct?
-}formulaContent::N3ParserRDFLabelformulaContent=dobNode<-newBlankNodepstate<-stGetstUpdate$\st->st{graphState=emptyRDFGraph,thisNode=bNode}statementListoldState<-restoreStatepstatestUpdate$updateGraph$setFormula(FormulabNode(graphStateoldState))returnbNodesubgraph::RDFLabel->N3ParserRDFGraphsubgraphthis=dopstate<-stGetstUpdate$\st->st{graphState=emptyRDFGraph,thisNode=this}statementsOptional-- parse statements of formulaoldState<-restoreStatepstatereturn$graphStateoldStatestatementList::N3Parser()statementList=ignore$sepEndBy(lexemestatement)fullStop{-
boolean ::= | "@false"
| "@true"
-}boolean::N3ParserRDFLabelboolean=mkTypedLitxsdBoolean<$>(atWord"false"<|>atWord"true")-- (try (atWord "false") <|> atWord "true"){-
dtlang ::= | "@" langcode
| "^^" symbol
| void
literal ::= | string dtlang
langcode ::= [a-z]+(-[a-z0-9]+)*
-}literal::N3ParserRDFLabelliteral=Lit<$>n3string<*>optionaldtlangdtlang::N3ParserScopedNamedtlang=(char'@'*>langcode)<|>string"^^"*>n3symbol-- <|> (try (string "^^") *> n3symbol)langcode::N3ParserScopedNamelangcode=doh<-many1Satisfyisazmt<-optional(L.append<$>(char'-'*>pure(L.singleton'-'))<*>many1Satisfyisaz09)return$langName$L.toStrict$L.appendh(fromMaybeL.emptymt){-
decimal ::= [-+]?[0-9]+(\.[0-9]+)?
double ::= [-+]?[0-9]+(\.[0-9]+)?([eE][-+]?[0-9]+)
integer ::= [-+]?[0-9]+
numericliteral ::= | decimal
| double
| integer
We actually support 1. for decimal values which isn't supported
by the above production.
TODO: we could convert via something like
maybeRead value :: Double >>= Just . toRDFLabel
which would mean we store the canonical XSD value in the
label, but it is not useful for the xsd:decimal case
since we currently don't have a Haskell type that
goes with it.
-}numericLiteral::N3ParserRDFLabelnumericLiteral=-- -- try (mkTypedLit xsdDouble <$> n3double)-- try (d2s <$> n3double)-- <|> try (mkTypedLit xsdDecimal <$> n3decimal)d2s<$>n3double<|>mkTypedLitxsdDecimal.T.pack<$>n3decimal<|>mkTypedLitxsdInteger.T.pack<$>n3integern3sign::N3ParserCharn3sign=char'+'<|>char'-'n3integer::N3ParserStringn3integer=doms<-optionaln3signds<-many1digitcasemsofJusts->return$s:ds_->returndsn3decimal::N3ParserString-- n3decimal = (++) <$> n3integer <*> ( (:) <$> char '.' <*> many1 digit )n3decimal=(++)<$>n3integer<*>((:)<$>char'.'<*>manydigit)n3double::N3ParserStringn3double=(++)<$>n3decimal<*>((:)<$>satisfy(`elem`"eE")<*>n3integer)-- Convert a double, as returned by n3double, into it's-- canonical XSD form. We assume that n3double returns-- a syntactivally valid Double, so do not bother with reads here--d2s::String->RDFLabeld2ss=toRDFLabel(reads::Double){-
propertylist ::= | verb object objecttail propertylisttail
| void
propertylisttail ::= | ";" propertylist
| void
-}-- it's probably important that bNode is created *after*-- processing the plist (mainly for the assumptions made by-- formatting the output as N3; e.g. list/sequence ordering)--propertyListBNode::N3ParserRDFLabelpropertyListBNode=doplist<-sepEndBy((,)<$>lexemeverb<*>objectList)semiColonbNode<-newBlankNodeletaddList((addFunc,vrb),items)=mapM_(addFuncbNodevrb)itemsforM_plistaddListreturnbNodepropertyListWith::RDFLabel->N3Parser()propertyListWithsubj=let-- term = lexeme verb >>= objectListWith subjterm=lexemeverb>>=\(addFunc,vrb)->objectListWith(addFuncsubjvrb)inignore$sepEndBytermsemiColon{-
object ::= | expression
objecttail ::= | "," object objecttail
| void
We change the production rule from objecttail to objectlist for lists of
objects (may change back).
-}object::N3ParserRDFLabelobject=lexemeexpressionobjectList::N3Parser[RDFLabel]objectList=sepBy1objectcommaobjectWith::AddStatement->N3Parser()objectWithaddFunc=object>>=addFuncobjectListWith::AddStatement->N3Parser()objectListWithaddFunc=ignore$sepBy1(objectWithaddFunc)comma{-
objectList1 :: N3Parser [RDFLabel]
objectList1 = sepBy1 object comma
-}{-
verb ::= | "<="
| "="
| "=>"
| "@a"
| "@has" expression
| "@is" expression "@of"
| expression
-}verb::N3Parser(RDFLabel->RDFLabel->AddStatement,RDFLabel)verb=-- we check reverse first so that <= is tried before looking for a URI via expression rule(,)addStatementRev<$>verbReverse<|>(,)addStatement<$>verbForward-- those verbs for which subject is on the right and object on the leftverbReverse::N3ParserRDFLabelverbReverse=string"<="*>operatorLabellogImplies<|>between(atWord"is")(atWord"of")(lexemeexpression){-
try (string "<=") *> operatorLabel logImplies
<|> between (try (atWord "is")) (atWord "of") (lexeme expression)
-}-- those verbs with subject on the left and object on the rightverbForward::N3ParserRDFLabelverbForward=-- (try (string "=>") *> operatorLabel logImplies)(string"=>"*>operatorLabellogImplies)<|>(string"="*>operatorLabelowlSameAs)-- <|> (try (atWord "a") *> operatorLabel rdfType)<|>(atWord"a"*>operatorLabelrdfType)<|>(atWord"has"*>lexemeexpression)<|>lexemeexpression{-
universal ::= | "@forAll" symbol_csl
TODO: what needs to be done to support universal quantification
-}universal::N3Parser()universal=-- try (atWord "forAll") *> atWord"forAll"*>failBad"universal (@forAll) currently unsupported."-- will be something like: *> symbolCsl------------------------------------------------------------------------------------ Copyright (c) 2003, Graham Klyne, 2009 Vasili I Galchin, 2011 Douglas Burke-- All rights reserved.---- This file is part of Swish.---- Swish is free software; you can redistribute it and/or modify-- it under the terms of the GNU General Public License as published by-- the Free Software Foundation; either version 2 of the License, or-- (at your option) any later version.---- Swish is distributed in the hope that it will be useful,-- but WITHOUT ANY WARRANTY; without even the implied warranty of-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the-- GNU General Public License for more details.---- You should have received a copy of the GNU General Public License-- along with Swish; if not, write to:-- The Free Software Foundation, Inc.,-- 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA----------------------------------------------------------------------------------