pro-source (empty) → 0.1.0.0
raw patch · 17 files changed
+991/−0 lines, 17 filesdep +basedep +deepseqdep +hashable
Dependencies added: base, deepseq, hashable, optics-core, prettyprinter, pro-source, tasty, tasty-hunit, tasty-quickcheck, text, vector
Files
- LICENSE +373/−0
- pro-source.cabal +94/−0
- src/Prelude.hs +16/−0
- src/ProSource.hs +34/−0
- src/ProSource/Column.hs +14/−0
- src/ProSource/HasLocation.hs +41/−0
- src/ProSource/Line.hs +17/−0
- src/ProSource/LineMap.hs +51/−0
- src/ProSource/Location.hs +28/−0
- src/ProSource/LocationOps.hs +41/−0
- src/ProSource/Offset.hs +40/−0
- src/ProSource/Source.hs +32/−0
- src/ProSource/SourceOps.hs +41/−0
- src/ProSource/SparseLocation.hs +20/−0
- src/ProSource/Units.hs +10/−0
- src/Text.hs +11/−0
- test/Main.hs +128/−0
+ LICENSE view
@@ -0,0 +1,373 @@+Mozilla Public License Version 2.0+==================================++1. Definitions+--------------++1.1. "Contributor"+ means each individual or legal entity that creates, contributes to+ the creation of, or owns Covered Software.++1.2. "Contributor Version"+ means the combination of the Contributions of others (if any) used+ by a Contributor and that particular Contributor's Contribution.++1.3. "Contribution"+ means Covered Software of a particular Contributor.++1.4. "Covered Software"+ means Source Code Form to which the initial Contributor has attached+ the notice in Exhibit A, the Executable Form of such Source Code+ Form, and Modifications of such Source Code Form, in each case+ including portions thereof.++1.5. "Incompatible With Secondary Licenses"+ means++ (a) that the initial Contributor has attached the notice described+ in Exhibit B to the Covered Software; or++ (b) that the Covered Software was made available under the terms of+ version 1.1 or earlier of the License, but not also under the+ terms of a Secondary License.++1.6. "Executable Form"+ means any form of the work other than Source Code Form.++1.7. "Larger Work"+ means a work that combines Covered Software with other material, in+ a separate file or files, that is not Covered Software.++1.8. "License"+ means this document.++1.9. "Licensable"+ means having the right to grant, to the maximum extent possible,+ whether at the time of the initial grant or subsequently, any and+ all of the rights conveyed by this License.++1.10. "Modifications"+ means any of the following:++ (a) any file in Source Code Form that results from an addition to,+ deletion from, or modification of the contents of Covered+ Software; or++ (b) any new file in Source Code Form that contains any Covered+ Software.++1.11. "Patent Claims" of a Contributor+ means any patent claim(s), including without limitation, method,+ process, and apparatus claims, in any patent Licensable by such+ Contributor that would be infringed, but for the grant of the+ License, by the making, using, selling, offering for sale, having+ made, import, or transfer of either its Contributions or its+ Contributor Version.++1.12. "Secondary License"+ means either the GNU General Public License, Version 2.0, the GNU+ Lesser General Public License, Version 2.1, the GNU Affero General+ Public License, Version 3.0, or any later versions of those+ licenses.++1.13. "Source Code Form"+ means the form of the work preferred for making modifications.++1.14. "You" (or "Your")+ means an individual or a legal entity exercising rights under this+ License. For legal entities, "You" includes any entity that+ controls, is controlled by, or is under common control with You. For+ purposes of this definition, "control" means (a) the power, direct+ or indirect, to cause the direction or management of such entity,+ whether by contract or otherwise, or (b) ownership of more than+ fifty percent (50%) of the outstanding shares or beneficial+ ownership of such entity.++2. License Grants and Conditions+--------------------------------++2.1. Grants++Each Contributor hereby grants You a world-wide, royalty-free,+non-exclusive license:++(a) under intellectual property rights (other than patent or trademark)+ Licensable by such Contributor to use, reproduce, make available,+ modify, display, perform, distribute, and otherwise exploit its+ Contributions, either on an unmodified basis, with Modifications, or+ as part of a Larger Work; and++(b) under Patent Claims of such Contributor to make, use, sell, offer+ for sale, have made, import, and otherwise transfer either its+ Contributions or its Contributor Version.++2.2. Effective Date++The licenses granted in Section 2.1 with respect to any Contribution+become effective for each Contribution on the date the Contributor first+distributes such Contribution.++2.3. Limitations on Grant Scope++The licenses granted in this Section 2 are the only rights granted under+this License. No additional rights or licenses will be implied from the+distribution or licensing of Covered Software under this License.+Notwithstanding Section 2.1(b) above, no patent license is granted by a+Contributor:++(a) for any code that a Contributor has removed from Covered Software;+ or++(b) for infringements caused by: (i) Your and any other third party's+ modifications of Covered Software, or (ii) the combination of its+ Contributions with other software (except as part of its Contributor+ Version); or++(c) under Patent Claims infringed by Covered Software in the absence of+ its Contributions.++This License does not grant any rights in the trademarks, service marks,+or logos of any Contributor (except as may be necessary to comply with+the notice requirements in Section 3.4).++2.4. Subsequent Licenses++No Contributor makes additional grants as a result of Your choice to+distribute the Covered Software under a subsequent version of this+License (see Section 10.2) or under the terms of a Secondary License (if+permitted under the terms of Section 3.3).++2.5. Representation++Each Contributor represents that the Contributor believes its+Contributions are its original creation(s) or it has sufficient rights+to grant the rights to its Contributions conveyed by this License.++2.6. Fair Use++This License is not intended to limit any rights You have under+applicable copyright doctrines of fair use, fair dealing, or other+equivalents.++2.7. Conditions++Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted+in Section 2.1.++3. Responsibilities+-------------------++3.1. Distribution of Source Form++All distribution of Covered Software in Source Code Form, including any+Modifications that You create or to which You contribute, must be under+the terms of this License. You must inform recipients that the Source+Code Form of the Covered Software is governed by the terms of this+License, and how they can obtain a copy of this License. You may not+attempt to alter or restrict the recipients' rights in the Source Code+Form.++3.2. Distribution of Executable Form++If You distribute Covered Software in Executable Form then:++(a) such Covered Software must also be made available in Source Code+ Form, as described in Section 3.1, and You must inform recipients of+ the Executable Form how they can obtain a copy of such Source Code+ Form by reasonable means in a timely manner, at a charge no more+ than the cost of distribution to the recipient; and++(b) You may distribute such Executable Form under the terms of this+ License, or sublicense it under different terms, provided that the+ license for the Executable Form does not attempt to limit or alter+ the recipients' rights in the Source Code Form under this License.++3.3. Distribution of a Larger Work++You may create and distribute a Larger Work under terms of Your choice,+provided that You also comply with the requirements of this License for+the Covered Software. If the Larger Work is a combination of Covered+Software with a work governed by one or more Secondary Licenses, and the+Covered Software is not Incompatible With Secondary Licenses, this+License permits You to additionally distribute such Covered Software+under the terms of such Secondary License(s), so that the recipient of+the Larger Work may, at their option, further distribute the Covered+Software under the terms of either this License or such Secondary+License(s).++3.4. Notices++You may not remove or alter the substance of any license notices+(including copyright notices, patent notices, disclaimers of warranty,+or limitations of liability) contained within the Source Code Form of+the Covered Software, except that You may alter any license notices to+the extent required to remedy known factual inaccuracies.++3.5. Application of Additional Terms++You may choose to offer, and to charge a fee for, warranty, support,+indemnity or liability obligations to one or more recipients of Covered+Software. However, You may do so only on Your own behalf, and not on+behalf of any Contributor. You must make it absolutely clear that any+such warranty, support, indemnity, or liability obligation is offered by+You alone, and You hereby agree to indemnify every Contributor for any+liability incurred by such Contributor as a result of warranty, support,+indemnity or liability terms You offer. You may include additional+disclaimers of warranty and limitations of liability specific to any+jurisdiction.++4. Inability to Comply Due to Statute or Regulation+---------------------------------------------------++If it is impossible for You to comply with any of the terms of this+License with respect to some or all of the Covered Software due to+statute, judicial order, or regulation then You must: (a) comply with+the terms of this License to the maximum extent possible; and (b)+describe the limitations and the code they affect. Such description must+be placed in a text file included with all distributions of the Covered+Software under this License. Except to the extent prohibited by statute+or regulation, such description must be sufficiently detailed for a+recipient of ordinary skill to be able to understand it.++5. Termination+--------------++5.1. The rights granted under this License will terminate automatically+if You fail to comply with any of its terms. However, if You become+compliant, then the rights granted under this License from a particular+Contributor are reinstated (a) provisionally, unless and until such+Contributor explicitly and finally terminates Your grants, and (b) on an+ongoing basis, if such Contributor fails to notify You of the+non-compliance by some reasonable means prior to 60 days after You have+come back into compliance. Moreover, Your grants from a particular+Contributor are reinstated on an ongoing basis if such Contributor+notifies You of the non-compliance by some reasonable means, this is the+first time You have received notice of non-compliance with this License+from such Contributor, and You become compliant prior to 30 days after+Your receipt of the notice.++5.2. If You initiate litigation against any entity by asserting a patent+infringement claim (excluding declaratory judgment actions,+counter-claims, and cross-claims) alleging that a Contributor Version+directly or indirectly infringes any patent, then the rights granted to+You by any and all Contributors for the Covered Software under Section+2.1 of this License shall terminate.++5.3. In the event of termination under Sections 5.1 or 5.2 above, all+end user license agreements (excluding distributors and resellers) which+have been validly granted by You or Your distributors under this License+prior to termination shall survive termination.++************************************************************************+* *+* 6. Disclaimer of Warranty *+* ------------------------- *+* *+* Covered Software is provided under this License on an "as is" *+* basis, without warranty of any kind, either expressed, implied, or *+* statutory, including, without limitation, warranties that the *+* Covered Software is free of defects, merchantable, fit for a *+* particular purpose or non-infringing. The entire risk as to the *+* quality and performance of the Covered Software is with You. *+* Should any Covered Software prove defective in any respect, You *+* (not any Contributor) assume the cost of any necessary servicing, *+* repair, or correction. This disclaimer of warranty constitutes an *+* essential part of this License. No use of any Covered Software is *+* authorized under this License except under this disclaimer. *+* *+************************************************************************++************************************************************************+* *+* 7. Limitation of Liability *+* -------------------------- *+* *+* Under no circumstances and under no legal theory, whether tort *+* (including negligence), contract, or otherwise, shall any *+* Contributor, or anyone who distributes Covered Software as *+* permitted above, be liable to You for any direct, indirect, *+* special, incidental, or consequential damages of any character *+* including, without limitation, damages for lost profits, loss of *+* goodwill, work stoppage, computer failure or malfunction, or any *+* and all other commercial damages or losses, even if such party *+* shall have been informed of the possibility of such damages. This *+* limitation of liability shall not apply to liability for death or *+* personal injury resulting from such party's negligence to the *+* extent applicable law prohibits such limitation. Some *+* jurisdictions do not allow the exclusion or limitation of *+* incidental or consequential damages, so this exclusion and *+* limitation may not apply to You. *+* *+************************************************************************++8. Litigation+-------------++Any litigation relating to this License may be brought only in the+courts of a jurisdiction where the defendant maintains its principal+place of business and such litigation shall be governed by laws of that+jurisdiction, without reference to its conflict-of-law provisions.+Nothing in this Section shall prevent a party's ability to bring+cross-claims or counter-claims.++9. Miscellaneous+----------------++This License represents the complete agreement concerning the subject+matter hereof. If any provision of this License is held to be+unenforceable, such provision shall be reformed only to the extent+necessary to make it enforceable. Any law or regulation which provides+that the language of a contract shall be construed against the drafter+shall not be used to construe this License against a Contributor.++10. Versions of the License+---------------------------++10.1. New Versions++Mozilla Foundation is the license steward. Except as provided in Section+10.3, no one other than the license steward has the right to modify or+publish new versions of this License. Each version will be given a+distinguishing version number.++10.2. Effect of New Versions++You may distribute the Covered Software under the terms of the version+of the License under which You originally received the Covered Software,+or under the terms of any subsequent version published by the license+steward.++10.3. Modified Versions++If you create software not governed by this License, and you want to+create a new license for such software, you may create and use a+modified version of this License if you rename the license and remove+any references to the name of the license steward (except to note that+such modified license differs from this License).++10.4. Distributing Source Code Form that is Incompatible With Secondary+Licenses++If You choose to distribute Source Code Form that is Incompatible With+Secondary Licenses under the terms of this version of the License, the+notice described in Exhibit B of this License must be attached.++Exhibit A - Source Code Form License Notice+-------------------------------------------++ This Source Code Form is subject to the terms of the Mozilla Public+ License, v. 2.0. If a copy of the MPL was not distributed with this+ file, You can obtain one at http://mozilla.org/MPL/2.0/.++If it is not possible or desirable to put the notice in a particular+file, then You may include the notice in a location (such as a LICENSE+file in a relevant directory) where a recipient would be likely to look+for such a notice.++You may add additional accurate notices of copyright ownership.++Exhibit B - "Incompatible With Secondary Licenses" Notice+---------------------------------------------------------++ This Source Code Form is "Incompatible With Secondary Licenses", as+ defined by the Mozilla Public License, v. 2.0.
+ pro-source.cabal view
@@ -0,0 +1,94 @@+cabal-version: 3.0++name: pro-source+version: 0.1.0.0++license: MPL-2.0+license-file: LICENSE+author: James Alexander Feldman-Crough+maintainer: hello@typeclasses.com+copyright: © 2019-2020 James Alexander Feldman-Crough++category: Parsing+synopsis: Utilities for tracking source locations+description:+ Forked from part of the+ [prosidy](https://hackage.haskell.org/package/prosidy)+ package.++source-repository head+ type: git+ location: git://github.com/typeclasses/pro-source.git++common base+ default-language: Haskell2010+ ghc-options: -Wall++ default-extensions:+ DeriveAnyClass DeriveGeneric+ DerivingStrategies GeneralizedNewtypeDeriving++ LambdaCase ViewPatterns++ MultiParamTypeClasses TypeFamilies++ OverloadedStrings++ ScopedTypeVariables TypeApplications++ build-depends:+ base ^>= 4.13 || ^>= 4.14 || ^>= 4.15 || ^>= 4.16+ , deepseq ^>= 1.4.4+ , hashable ^>= 1.3 || ^>= 1.4+ , optics-core ^>= 0.3 || ^>= 0.4+ , prettyprinter ^>= 1.6 || ^>= 1.7+ , text ^>= 1.2+ , vector ^>= 0.12++library+ import: base+ hs-source-dirs: src++ exposed-modules:+ ProSource+ ProSource.HasLocation+ ProSource.Location+ ProSource.LocationOps+ ProSource.Line+ ProSource.LineMap+ ProSource.Column+ ProSource.Offset+ ProSource.Source+ ProSource.SourceOps+ ProSource.SparseLocation+ ProSource.Units++ other-modules: Prelude Text++ mixins:+ base ( Prelude as BasePrelude+ , Control.Monad+ , Data.Foldable+ , Data.List+ , GHC.Generics+ )+ , deepseq ( Control.DeepSeq )+ , hashable ( Data.Hashable )+ , optics-core ( Optics.Core )+ , prettyprinter ( Prettyprinter )+ , text ( Data.Text, Data.Text.Lazy )+ , vector ( Data.Vector.Generic+ , Data.Vector.Generic.Mutable+ , Data.Vector.Unboxed )++test-suite pro-source-test+ import: base+ type: exitcode-stdio-1.0+ hs-source-dirs: test+ main-is: Main.hs++ build-depends:+ pro-source+ , tasty ^>= 1.2 || ^>= 1.3 || ^>= 1.4+ , tasty-hunit ^>= 0.10+ , tasty-quickcheck ^>= 0.10
+ src/Prelude.hs view
@@ -0,0 +1,16 @@+{-# LANGUAGE NoImplicitPrelude #-}++module Prelude (module X) where++import BasePrelude as X (Enum, Eq, Functor (fmap), Maybe (..), Monoid, Ord (compare, (<), (>), (>=), (>=)), Ordering (..), Semigroup ((<>)), Show (..), String, Word, div, drop, flip, fromEnum, fromIntegral, id, maybe, mconcat, otherwise, pred, succ, ($), (&&), (+), (-), (.), (/=), (<$>), (==), (||))+import Control.DeepSeq as X (NFData)+import Control.Monad as X (guard)+import Data.Foldable as X (Foldable (toList))+import Data.Hashable as X (Hashable (..))+import Data.List as X (sort)+import Data.Text as X (Text)+import GHC.Generics as X (Generic)+import Optics.Core as X (AffineFold, AffineTraversal', Iso', castOptic, iso, lens, simple, to, (%))+import Prettyprinter as X (Pretty (..), (<+>))++import Text as X
+ src/ProSource.hs view
@@ -0,0 +1,34 @@+{- |+Description: Utilities for tracking source locations.+Copyright: © 2020 James Alexander Feldman-Crough+License: MPL-2.0+-}+module ProSource+ (+ -- * Source+ Source(..)+ , makeSource, getSourceLine, getLocation++ -- * Location+ , Location (..), SparseLocation(..)+ , enrichLocation, stripLocation, sparse++ -- * Units+ , Offset(..), Line(..), Column(..)++ -- * Line map+ , LineMap, lineOffsets, lineToOffset, offsetToLine++ -- * Polymorphic Location optics+ , HasLocation(..), offset, column, line, source++ ) where++import ProSource.HasLocation+import ProSource.LineMap+import ProSource.Location+import ProSource.LocationOps+import ProSource.Source+import ProSource.SourceOps+import ProSource.SparseLocation+import ProSource.Units
+ src/ProSource/Column.hs view
@@ -0,0 +1,14 @@+{- |+Copyright: © 2019 James Alexander Feldman-Crough+License: MPL-2.0+-}+module ProSource.Column (Column (..)) where++-- | A column number.+newtype Column = Column Word+ deriving stock (Eq, Ord, Generic, Show)+ deriving anyclass (Hashable, NFData)+ deriving newtype Enum++instance Pretty Column where+ pretty (Column n) = pretty $ succ n
+ src/ProSource/HasLocation.hs view
@@ -0,0 +1,41 @@+{- |+Copyright: © 2020 James Alexander Feldman-Crough+License: MPL-2.0+-}+module ProSource.HasLocation+ ( -- * Classy optics; implementable on all types with a location+ HasLocation(..), offset+ -- ** Read-only optics+ , column, line, source+ ) where++import ProSource.Location+import ProSource.Units+import ProSource.SparseLocation+import ProSource.Source+import ProSource.LocationOps++-- | A classy optic for selecting the 'Location' from a value. Note that 'location' is affine: a 'Location' can't be attached to a value which does not -- already have one, and not all values with an instance of 'HasLocation' have a location.+class HasLocation t where+ location :: AffineTraversal' t Location++instance HasLocation Location where+ location = castOptic simple++-- | Focus on the 'Offset' from a value parsed from a source file. If the 'Offset' is modified, note that the resulting 'column' and 'line' will /also/ be modified as they are denormalizations of this value.+offset :: HasLocation l => AffineTraversal' l Offset+offset = location % sparse % lens+ sparseLocationOffset+ (\sl x -> sl { sparseLocationOffset = x })++-- | Fetch the 'Column' from a value parsed from a source file. Modifications are not allowed as the 'offset' and 'line' may become inconsistent.+column :: HasLocation l => AffineFold l Column+column = location % to locationColumn++-- | Fetch the 'Line' from a value parsed from a source file. Modifications are not allowed as the 'offset' and 'column' may become inconsistent.+line :: HasLocation l => AffineFold l Line+line = location % to locationLine++-- | Fetch the 'Source' a value was parsed from. Modifications are not allowed as the 'line', 'offset', and 'column' may become inconsistent.+source :: HasLocation l => AffineFold l Source+source = location % to locationSource
+ src/ProSource/Line.hs view
@@ -0,0 +1,17 @@+{- |+Copyright: © 2019 James Alexander Feldman-Crough+License: MPL-2.0+-}+module ProSource.Line (Line (..)) where++{- | A line number.++The 'Show' instance for 'Line' counts from one, while the internal implementation counts from zero.+-}+newtype Line = Line Word+ deriving stock (Eq, Ord, Generic, Show)+ deriving anyclass (Hashable, NFData)+ deriving newtype Enum++instance Pretty Line where+ pretty (Line n) = pretty $ succ n
+ src/ProSource/LineMap.hs view
@@ -0,0 +1,51 @@+{- |+Description: Binary-search tree for finding the position of new lines.+Copyright: © 2019 James Alexander Feldman-Crough+License: MPL-2.0+-}+module ProSource.LineMap+ ( LineMap+ , lineOffsets+ , lineToOffset+ , offsetToLine+ , fromOffsets+ ) where++import Data.Vector.Unboxed (Vector)+import qualified Data.Vector.Unboxed as V++import ProSource.Units++-- | A dense vector containing offsets poiting to the start of each line. That is, the starting position of the third line of a file can be found at position 2.+newtype LineMap = LineMap (Vector Offset)+ deriving stock (Eq, Generic)+ deriving newtype (Show, NFData)++instance Hashable LineMap where+ hashWithSalt salt (LineMap v) = V.foldl' hashWithSalt salt v++fromOffsets :: Foldable f => f Offset -> LineMap+fromOffsets = LineMap . V.fromList . sort . toList++-- | Convert a 'LineMap' into a list of 'Offset's, corresponding to the first character of a line. Note that the initial offset is omitted-- the offset at index 0 will be the offset of the /second/ line.+lineOffsets :: LineMap -> [Offset]+lineOffsets (LineMap v) = V.toList v++-- | Fetch the 'Offset' for the given 'Line'. Evaluates to 'Nothing' if the given 'Line' does not appear in the LineMap+lineToOffset :: Line -> LineMap -> Maybe Offset+lineToOffset = \case+ Line 0 -> \_ -> Just $ Offset 0+ Line nth -> \(LineMap xs) -> xs V.!? fromIntegral (pred nth)++-- | Fetch the 'Line' number for a given 'Offset'. Newlines will be attributed the line that they terminate, rather than the line started immediately afterwards.+offsetToLine :: Offset -> LineMap -> Line+offsetToLine offset (LineMap xs) = Line $ fromIntegral $ go Nothing 0 (V.length xs)+ where+ go result min max | min >= max = maybe 0 succ result+ go result min max = case compare nthOffset offset of+ EQ -> succ nthIndex+ LT -> go (Just nthIndex) (nthIndex + 1) max+ GT -> go result min nthIndex+ where+ nthIndex = ((max - min) `div` 2) + min+ nthOffset = xs V.! nthIndex
+ src/ProSource/Location.hs view
@@ -0,0 +1,28 @@+{-# LANGUAGE StrictData #-}++{- |+Copyright: © 2019 James Alexander Feldman-Crough+License: MPL-2.0+-}+module ProSource.Location (Location (..)) where++import ProSource.Source+import ProSource.Units++-- | A location in a 'Source', with the line and column number computed lazily.+data Location = Location+ { locationSource :: Source+ -- ^ The 'Source' this location references.+ , locationOffset :: Offset+ -- ^ The position in the 'Source', counted by Unicode codepoints.+ , locationLine :: ~Line+ -- ^ The line number in the 'Source'.+ , locationColumn :: ~Column+ -- ^ The column number in the 'Source'.+ }+ deriving stock (Show, Generic, Eq)+ deriving anyclass (NFData, Hashable)++instance Pretty Location where+ pretty loc = pretty (locationSource loc) <+> "@" <+> mconcat+ [pretty (locationLine loc), "×", pretty (locationColumn loc)]
+ src/ProSource/LocationOps.hs view
@@ -0,0 +1,41 @@+{- |+Copyright: © 2019 James Alexander Feldman-Crough+License: MPL-2.0+-}+module ProSource.LocationOps (enrichLocation, stripLocation, sparse) where++import ProSource.Column+import ProSource.LineMap+import ProSource.Location+import ProSource.Offset+import ProSource.Source+import ProSource.SparseLocation++-- | Add lazily computed line and column number information to a 'SparseLocation'.+enrichLocation :: SparseLocation -> Location+enrichLocation sl = Location+ { locationSource = source+ , locationOffset = offset+ , locationLine = line+ , locationColumn = column+ }+ where+ source = sparseLocationSource sl+ lineMap = sourceLineMap source+ offset@(~(Offset offsetN)) = sparseLocationOffset sl+ line = offsetToLine offset lineMap++ column = case lineToOffset line lineMap of+ Just (Offset n) -> Column (offsetN - n)+ Nothing -> Column 0++-- | Remove line and column number information from a 'Location'.+stripLocation :: Location -> SparseLocation+stripLocation l = SparseLocation+ { sparseLocationSource = locationSource l+ , sparseLocationOffset = locationOffset l+ }++-- | An isomorphism between 'Location' and 'SparseLocation'. This is allowed because although a 'Location' has strictly more data than a 'SparseLocation', those values are denormalizations of values within 'SparseLocation'.+sparse :: Iso' Location SparseLocation+sparse = iso stripLocation enrichLocation
+ src/ProSource/Offset.hs view
@@ -0,0 +1,40 @@+{- |+Copyright: © 2019 James Alexander Feldman-Crough+License: MPL-2.0+-}+module ProSource.Offset (Offset (..)) where++import qualified Data.Vector.Generic as VG+import qualified Data.Vector.Generic.Mutable as VGM+import Data.Vector.Unboxed (MVector, Unbox, Vector)++-- | An offset into a 'Source', counted by UTF-8 codepoint.+newtype Offset = Offset Word+ deriving stock (Eq, Show, Ord, Generic)+ deriving anyclass (Hashable, NFData)+ deriving newtype Enum++instance Pretty Offset where+ pretty (Offset n) = "+" <> pretty n++newtype instance MVector s Offset = MV_Offset (MVector s Word)++instance VGM.MVector MVector Offset where+ basicLength (MV_Offset m) = VGM.basicLength m+ basicUnsafeSlice ix len (MV_Offset m) = MV_Offset $ VGM.basicUnsafeSlice ix len m+ basicOverlaps (MV_Offset x) (MV_Offset y) = VGM.basicOverlaps x y+ basicUnsafeNew len = MV_Offset <$> VGM.basicUnsafeNew len+ basicInitialize (MV_Offset v) = VGM.basicInitialize v+ basicUnsafeRead (MV_Offset v) = fmap Offset <$> VGM.basicUnsafeRead v+ basicUnsafeWrite (MV_Offset v) ix (Offset w) = VGM.basicUnsafeWrite v ix w++newtype instance Vector Offset = V_Offset (Vector Word)++instance VG.Vector Vector Offset where+ basicUnsafeFreeze (MV_Offset v) = V_Offset <$> VG.basicUnsafeFreeze v+ basicUnsafeThaw (V_Offset v) = MV_Offset <$> VG.basicUnsafeThaw v+ basicLength (V_Offset v) = VG.basicLength v+ basicUnsafeSlice ix len (V_Offset v) = V_Offset $ VG.basicUnsafeSlice ix len v+ basicUnsafeIndexM (V_Offset v) ix = Offset <$> VG.basicUnsafeIndexM v ix++instance Unbox Offset where
+ src/ProSource/Source.hs view
@@ -0,0 +1,32 @@+{-# LANGUAGE StrictData #-}++{- |+Copyright: © 2019 James Alexander Feldman-Crough+License: MPL-2.0+-}+module ProSource.Source (Source (..)) where++import ProSource.LineMap++{- | Information about a source file.++The 'Show' instance for ths class does not include the 'LineMap' or 'Text' fields, as those are rather noisy.+-}+data Source = Source+ { sourceName :: String+ -- ^ The reported file-name of the 'Source'.+ --+ -- When read from file handles, a non-filepath description such as @"\<stdin\>"@ is typically chosen. This field doesn't have semantic meaning, and should only be used to enrich the output displayed to users.+ , sourceText :: Text+ -- ^ The full source, as 'Text'.+ , sourceLineMap :: LineMap+ -- ^ A mapping of the start position of each line in the 'Source'.+ }+ deriving stock (Eq, Generic)+ deriving anyclass (Hashable, NFData)++instance Show Source where+ show (Source fp _ _) = "Source " <> show fp++instance Pretty Source where+ pretty = pretty . sourceName
+ src/ProSource/SourceOps.hs view
@@ -0,0 +1,41 @@+{- |+Copyright: © 2019 James Alexander Feldman-Crough+License: MPL-2.0+-}+module ProSource.SourceOps (makeSource, getLocation, getSourceLine) where++import qualified Data.Text as T++import ProSource.Line+import ProSource.LineMap+import ProSource.Location+import ProSource.LocationOps+import ProSource.Offset+import ProSource.Source+import ProSource.SparseLocation++-- | Create a 'Source' from a descriptive name and a body. The source name is typically a 'FilePath', but this is not guaranteed. For instance, when read from standard-input, a common choice is to name the source @\<stdin\>@.+makeSource :: String -> LText -> Source+makeSource name (toStrictText -> body) = Source name body lineMap+ where+ lineMap = case T.foldl' lineMapFold (1, '\0', []) $ body of+ (_, _, acc) -> fromOffsets acc+ lineMapFold (ix, prev, acc) ch+ | ch == '\n' && prev == '\r' = (succ ix, ch, Offset ix : drop 1 acc)+ | ch == '\n' || ch == '\r' = (succ ix, ch, Offset ix : acc)+ | otherwise = (succ ix, ch, acc)++-- | Convert an 'Offset' into a 'Location'.+getLocation :: Offset -> Source -> Maybe Location+getLocation offset src = do+ guard $ T.length (sourceText src) > fromEnum offset+ Just . enrichLocation $ SparseLocation src offset++-- | Fetch a single line from a source.+getSourceLine :: Line -> Source -> Maybe Text+getSourceLine line source = do+ start <- fromEnum <$> lineToOffset line lineMap+ let end = fromEnum <$> lineToOffset (succ line) lineMap+ Just . maybe id (T.take . (flip (-) start)) end . T.drop start $ sourceText+ source+ where lineMap = sourceLineMap source
+ src/ProSource/SparseLocation.hs view
@@ -0,0 +1,20 @@+{-# LANGUAGE StrictData #-}++{- |+Copyright: © 2019 James Alexander Feldman-Crough+License: MPL-2.0+-}+module ProSource.SparseLocation (SparseLocation (..)) where++import ProSource.Offset+import ProSource.Source++-- | A location in a 'Source'. The line and column numbers of this type are not attached to this type; convert to a 'Location' to access those values.+data SparseLocation = SparseLocation+ { sparseLocationSource :: Source+ -- ^ The 'Source' this location references.+ , sparseLocationOffset :: Offset+ -- ^ The position in the 'Source', counted by Unicode codepoints.+ }+ deriving stock (Show, Generic, Eq)+ deriving anyclass (NFData, Hashable)
+ src/ProSource/Units.hs view
@@ -0,0 +1,10 @@+{- |+Description: Positional units for marking source-code locations.+Copyright: © 2019 James Alexander Feldman-Crough+License: MPL-2.0+-}+module ProSource.Units (module X) where++import ProSource.Column as X+import ProSource.Line as X+import ProSource.Offset as X
+ src/Text.hs view
@@ -0,0 +1,11 @@+{-# language NoImplicitPrelude #-}++module Text where++import Data.Text (Text)+import qualified Data.Text.Lazy as Text.Lazy++type LText = Text.Lazy.Text++toStrictText :: LText -> Text+toStrictText = Text.Lazy.toStrict
+ test/Main.hs view
@@ -0,0 +1,128 @@+{- |+Copyright: © 2020 James Alexander Feldman-Crough+License: MPL-2.0+-}+module Main (main) where++import Data.IORef+import System.IO.Unsafe+import Test.Tasty+import Test.Tasty.HUnit+import Test.Tasty.QuickCheck++import Control.Exception (evaluate)+import Data.Foldable (for_, toList)+import Prelude hiding (lines)++import qualified Data.Text as T+import qualified Data.Text.Lazy as LT+import qualified ProSource as PS++main :: IO ()+main = defaultMain $ testGroup "test" [tests]++tests :: TestTree+tests = testGroup+ "source"+ [ testEmptyLineMap+ , testSimpleLineMap+ , testLineDelimiters+ , testLocation+ , testLocationLazy+ , propLineOffset+ ]++testEmptyLineMap :: TestTree+testEmptyLineMap = testCase "empty" $ do+ let source = PS.makeSource "<test>" ""+ assertBool "An empty source generates an empty line map."+ $ null (PS.lineOffsets $ PS.sourceLineMap source)++testSimpleLineMap :: TestTree+testSimpleLineMap = testCase "simple" $ do+ let sourceLines =+ [ "This source file contains a few lines."+ , "Some, line the next, are empty."+ , ""+ , "Multiple consecutive empty lines are fine as well."+ , ""+ , ""+ , "終わり"+ ]+ source = PS.makeSource "<test>" $ LT.unlines (map LT.fromStrict sourceLines)+ toList (PS.lineOffsets $ PS.sourceLineMap source)+ @?= fmap PS.Offset [39, 71, 72, 123, 124, 125, 129]+ for_ (zip [PS.Line 0 ..] sourceLines) $ \(lineNumber, line) ->+ PS.getSourceLine lineNumber source @?= Just (line <> "\n")++testLineDelimiters :: TestTree+testLineDelimiters = testCase "endings" $ do+ let source = PS.makeSource "<test>" "abc\ndef\rghi\r\njkl"+ lines = PS.sourceLineMap source+ PS.getSourceLine (PS.Line 0) source @?= Just "abc\n"+ PS.lineToOffset (PS.Line 0) lines @?= Just (PS.Offset 0)+ PS.getSourceLine (PS.Line 1) source @?= Just "def\r"+ PS.lineToOffset (PS.Line 1) lines @?= Just (PS.Offset 4)+ PS.getSourceLine (PS.Line 2) source @?= Just "ghi\r\n"+ PS.lineToOffset (PS.Line 2) lines @?= Just (PS.Offset 8)+ PS.getSourceLine (PS.Line 3) source @?= Just "jkl"+ PS.lineToOffset (PS.Line 3) lines @?= Just (PS.Offset 13)+ PS.getSourceLine (PS.Line 4) source @?= Nothing++testLocation :: TestTree+testLocation = testCase "location" $ do+ let source = PS.makeSource "<test>" "ab\ncd\nef"+ checkLocation offset line column = do+ let loc = PS.getLocation (PS.Offset offset) source+ fmap PS.locationOffset loc @?= Just (PS.Offset offset)+ fmap PS.locationLine loc @?= Just (PS.Line line)+ fmap PS.locationColumn loc @?= Just (PS.Column column)+ checkLocation 0 0 0+ checkLocation 1 0 1+ checkLocation 2 0 2+ checkLocation 3 1 0+ checkLocation 4 1 1+ checkLocation 5 1 2+ checkLocation 6 2 0+ checkLocation 7 2 1+ PS.getLocation (PS.Offset 8) source @?= Nothing++testLocationLazy :: TestTree+testLocationLazy = testCase "lazy" $ do+ let source = PS.makeSource "<test>" "abc\ndef"+ Just loc = PS.getLocation (PS.Offset 5) source+ (line, checkLine) <- checkEvaluated (PS.locationLine loc)+ (col , checkCol ) <- checkEvaluated (PS.locationColumn loc)+ assertBool "line is unevaluated" . not =<< checkLine+ assertBool "col is unevaluated" . not =<< checkCol+ _ <- evaluate line+ assertBool "line is evaluated" =<< checkLine+ assertBool "col is unevaluated" . not =<< checkCol+ _ <- evaluate col+ assertBool "line is evaluated" =<< checkLine+ assertBool "col is evaluated" =<< checkCol++propLineOffset :: TestTree+propLineOffset =+ testProperty "line-and-offset" $ forAll gen $ \(source, initialOffset) ->+ let lineMap = PS.sourceLineMap source+ line = PS.offsetToLine initialOffset lineMap+ Just offset = PS.lineToOffset line lineMap+ line' = PS.offsetToLine offset lineMap+ in (initialOffset >= offset) .&&. (line === line')+ where+ gen = do+ text <- T.pack <$> genChar+ initialOffset <-+ elements+ [PS.Offset 0 .. toEnum+ (if T.null text then 0 else T.length text - 1)]+ pure (PS.makeSource "<text>" (LT.fromStrict text), initialOffset)+ genChar =+ listOf $ frequency+ [(10, elements ['a' .. 'z']), (4, pure ' '), (1, pure '\n')]++checkEvaluated :: a -> IO (a, IO Bool)+checkEvaluated val = do+ ref <- newIORef False+ pure (unsafePerformIO (writeIORef ref True) `seq` val, readIORef ref)