Compare commits
191
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a05904b226 | ||
|
|
c181a9185a | ||
|
|
6a597b41a6 | ||
|
|
c85d8d8b4e | ||
|
|
dcdb996085 | ||
|
|
5968f54f55 | ||
|
|
8dca3ef10d | ||
|
|
04daafbde3 | ||
|
|
75421cbfe3 | ||
|
|
b0aa62f2b7 | ||
|
|
3ae8fb7371 | ||
|
|
73c657cdc4 | ||
|
|
2b48ee4a8f | ||
|
|
8ba2cef913 | ||
|
|
dcc93042d3 | ||
|
|
34ff993059 | ||
|
|
b660bd2be6 | ||
|
|
1db51e66bc | ||
|
|
c15e51ffb1 | ||
|
|
84026f0513 | ||
|
|
c0a2f1ab47 | ||
|
|
616e8f9924 | ||
|
|
a88d53d2fd | ||
|
|
5f6a95c7b5 | ||
|
|
fadeb66365 | ||
|
|
c8bbfed041 | ||
|
|
e9c7548b82 | ||
|
|
67f8ad9435 | ||
|
|
5b45184a1a | ||
|
|
11965aaa2e | ||
|
|
ed7f95a492 | ||
|
|
63d10433c9 | ||
|
|
ea9db414ed | ||
|
|
b6f2fbbe62 | ||
|
|
125e278502 | ||
|
|
251f221b85 | ||
|
|
9e743d517a | ||
|
|
2dad2bdd97 | ||
|
|
d56370f734 | ||
|
|
334a79b704 | ||
|
|
4c65847568 | ||
|
|
4747f8681b | ||
|
|
3e3fe51959 | ||
|
|
924aeda504 | ||
|
|
f19ba32fdd | ||
|
|
0923c79fee | ||
|
|
50bcbc2271 | ||
|
|
1b556315af | ||
|
|
ecc8998d38 | ||
|
|
72691b4909 | ||
|
|
5e25c2a259 | ||
|
|
033d9b2fd5 | ||
|
|
0adf4638d1 | ||
|
|
e40778727b | ||
|
|
5a64a29774 | ||
|
|
45b6fc8148 | ||
|
|
8ad3b6f9b9 | ||
|
|
409456f581 | ||
|
|
553de39b2a | ||
|
|
b193bca563 | ||
|
|
56aa4cf1df | ||
|
|
2a610da2d1 | ||
|
|
93f80b2726 | ||
|
|
794d29a430 | ||
|
|
3c8f5ea251 | ||
|
|
c0f1c7dc1a | ||
|
|
a737a67eda | ||
|
|
48fb7c61b3 | ||
|
|
f2601ab4f0 | ||
|
|
2620e21dcc | ||
|
|
ce3328f11e | ||
|
|
c53e001973 | ||
|
|
976e4ff711 | ||
|
|
6e64cdcbfc | ||
|
|
73e479e171 | ||
|
|
758da604cd | ||
|
|
95a0074edf | ||
|
|
32e1b114df | ||
|
|
afec2544f1 | ||
|
|
6828ec9852 | ||
|
|
b3557429eb | ||
|
|
0618e61e0b | ||
|
|
a2643cc0d5 | ||
|
|
359d5f33bd | ||
|
|
fcd7cf4ddd | ||
|
|
1f4a5d8d9f | ||
|
|
92ab193369 | ||
|
|
fc57cf702c | ||
|
|
f51eca20f5 | ||
|
|
9887cb57b2 | ||
|
|
040604b7db | ||
|
|
26c7132237 | ||
|
|
483f6f2134 | ||
|
|
c463877e17 | ||
|
|
71ab537ee9 | ||
|
|
134b062be7 | ||
|
|
3898f744dc | ||
|
|
305059ff9f | ||
|
|
5863160112 | ||
|
|
fbf7bc835b | ||
|
|
04e85e11de | ||
|
|
20e45f363c | ||
|
|
46af5238ce | ||
|
|
cf134909fd | ||
|
|
a4eea165bb | ||
|
|
25cbab056b | ||
|
|
8041ce3119 | ||
|
|
8f0c91c032 | ||
|
|
940f8c4270 | ||
|
|
5b9bb64a88 | ||
|
|
7d8d97933f | ||
|
|
f13865d7a1 | ||
|
|
49cf2e67fe | ||
|
|
cbdeddbab8 | ||
|
|
eab9c4e587 | ||
|
|
830610cef8 | ||
|
|
297a0679d1 | ||
|
|
fe33150b7f | ||
|
|
3db27b01a1 | ||
|
|
326771f4b4 | ||
|
|
9c15ad2021 | ||
|
|
84ea129f26 | ||
|
|
50ebb036c4 | ||
|
|
539156430a | ||
|
|
cc2fb20ca0 | ||
|
|
df195b3187 | ||
|
|
f6c6ba95ad | ||
|
|
d9d6f43407 | ||
|
|
51a44edc63 | ||
|
|
062a6aa6d7 | ||
|
|
4c7bede5a4 | ||
|
|
0ce437ea97 | ||
|
|
ac3f8bcc52 | ||
|
|
95dd64b0f0 | ||
|
|
4d47f8f1e1 | ||
|
|
3895bbf856 | ||
|
|
aa3ebdfe9b | ||
|
|
6f3abbac62 | ||
|
|
65ce73deb4 | ||
|
|
e718bd50b6 | ||
|
|
a3e509034b | ||
|
|
0159c0ecc5 | ||
|
|
649cac791a | ||
|
|
5c25c90657 | ||
|
|
c2593dc417 | ||
|
|
e258d20ba2 | ||
|
|
a4ff3d4670 | ||
|
|
e6a85737da | ||
|
|
0dfaebcdcb | ||
|
|
0fd7d4b30c | ||
|
|
58a9b0cfe1 | ||
|
|
da5d653de8 | ||
|
|
6e6ea50763 | ||
|
|
c5ab3b2336 | ||
|
|
328c4a13f1 | ||
|
|
5add2c2ceb | ||
|
|
520c7bb15e | ||
|
|
32e324a283 | ||
|
|
eea05ff924 | ||
|
|
c9dbe1aff8 | ||
|
|
a8c3648ca5 | ||
|
|
d895d39ba9 | ||
|
|
8fe9c8a22a | ||
|
|
d8fad1ed9b | ||
|
|
cf004c2b3a | ||
|
|
6041fd7661 | ||
|
|
52487ff812 | ||
|
|
22ffc0fdfc | ||
|
|
e3710c0d1f | ||
|
|
4f747e6fff | ||
|
|
9da3e0d373 | ||
|
|
f72c5ffac0 | ||
|
|
2cb9a6e61d | ||
|
|
22f8aed31e | ||
|
|
e98624e5a9 | ||
|
|
92654f8204 | ||
|
|
66fd702363 | ||
|
|
9e544c55dc | ||
|
|
f25cb16e6b | ||
|
|
463b175cdb | ||
|
|
dc6cf2fb58 | ||
|
|
6c8b55793a | ||
|
|
a847d2250f | ||
|
|
befb103a12 | ||
|
|
2086e59ab1 | ||
|
|
79d81f4bee | ||
|
|
c128c2029e | ||
|
|
dd0d340eba | ||
|
|
15ad640202 | ||
|
|
fd738bface | ||
|
|
2002dc1a2f |
No files matched your search
@@ -0,0 +1,2 @@
|
||||
.gitattributes export-ignore
|
||||
.gitignore export-ignore
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
*.o
|
||||
*~
|
||||
|
||||
# Autogenerated
|
||||
*.gen.*
|
||||
|
||||
# Test binaries
|
||||
jv_test
|
||||
jv_parse
|
||||
parsertest*~
|
||||
|
||||
build
|
||||
|
||||
# Something delightfully recursive happens otherwise
|
||||
docs/content/2.download/source/*
|
||||
@@ -0,0 +1,70 @@
|
||||
jq is copyright (C) 2012 Stephen Dolan
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
"Software"), to deal in the Software without restriction, including
|
||||
without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to
|
||||
permit persons to whom the Software is furnished to do so, subject to
|
||||
the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
|
||||
|
||||
jq's documentation (everything found under the docs/ subdirectory in
|
||||
the source tree) is licensed under the Creative Commons CC BY 3.0
|
||||
license, which can be found at:
|
||||
|
||||
http://creativecommons.org/licenses/by/3.0/
|
||||
|
||||
The documentation website includes a copy of Twitter's Boostrap and
|
||||
relies on Bonsai, Liquid templates and various other projects, look
|
||||
them up for detailed licensing conditions.
|
||||
|
||||
|
||||
|
||||
jq incorporates David M. Gay's dtoa.c and g_fmt.c, which bear the
|
||||
following notices:
|
||||
|
||||
dtoa.c:
|
||||
The author of this software is David M. Gay.
|
||||
|
||||
Copyright (c) 1991, 2000, 2001 by Lucent Technologies.
|
||||
|
||||
Permission to use, copy, modify, and distribute this software for any
|
||||
purpose without fee is hereby granted, provided that this entire notice
|
||||
is included in all copies of any software which is or includes a copy
|
||||
or modification of this software and in all copies of the supporting
|
||||
documentation for such software.
|
||||
|
||||
THIS SOFTWARE IS BEING PROVIDED "AS IS", WITHOUT ANY EXPRESS OR IMPLIED
|
||||
WARRANTY. IN PARTICULAR, NEITHER THE AUTHOR NOR LUCENT MAKES ANY
|
||||
REPRESENTATION OR WARRANTY OF ANY KIND CONCERNING THE MERCHANTABILITY
|
||||
OF THIS SOFTWARE OR ITS FITNESS FOR ANY PARTICULAR PURPOSE.
|
||||
|
||||
g_fmt.c:
|
||||
The author of this software is David M. Gay.
|
||||
|
||||
Copyright (c) 1991, 1996 by Lucent Technologies.
|
||||
|
||||
Permission to use, copy, modify, and distribute this software for any
|
||||
purpose without fee is hereby granted, provided that this entire notice
|
||||
is included in all copies of any software which is or includes a copy
|
||||
or modification of this software and in all copies of the supporting
|
||||
documentation for such software.
|
||||
|
||||
THIS SOFTWARE IS BEING PROVIDED "AS IS", WITHOUT ANY EXPRESS OR IMPLIED
|
||||
WARRANTY. IN PARTICULAR, NEITHER THE AUTHOR NOR LUCENT MAKES ANY
|
||||
REPRESENTATION OR WARRANTY OF ANY KIND CONCERNING THE MERCHANTABILITY
|
||||
OF THIS SOFTWARE OR ITS FITNESS FOR ANY PARTICULAR PURPOSE.
|
||||
|
||||
@@ -1,304 +0,0 @@
|
||||
module JQ where
|
||||
import Text.JSON
|
||||
import Text.JSON.String
|
||||
import Data.Maybe
|
||||
import Data.Char
|
||||
import Data.List (sortBy,sort,groupBy,partition,intercalate)
|
||||
import Data.Function (on)
|
||||
import Data.Ord (comparing)
|
||||
import Control.Monad
|
||||
import Control.Monad.Writer
|
||||
import Control.Monad.List
|
||||
import Control.Monad.Reader
|
||||
import qualified Data.Map as M
|
||||
import qualified Data.Set as S
|
||||
import Data.Map ((!))
|
||||
import Debug.Trace
|
||||
|
||||
type Path = [Either Int String]
|
||||
|
||||
type Program = JSValue -> [(JSValue, Path)]
|
||||
|
||||
type Filter = JQ JSValue
|
||||
newtype Operator = Operator {runOperator:: [Filter] -> Filter}
|
||||
|
||||
type JQ = ReaderT (JSValue, M.Map String JSValue, M.Map (String, Int) Operator) (WriterT Path [])
|
||||
|
||||
runJQ :: JQ a -> JSValue -> [a]
|
||||
runJQ prog val = map fst $ runWriterT $ runReaderT prog (val,M.empty,primitives)
|
||||
|
||||
(>|) :: JQ JSValue -> JQ a -> JQ a
|
||||
a >| b = do
|
||||
val <- a
|
||||
local (\(v,s,d) -> (val,s,d)) b
|
||||
|
||||
setvar name val prog =
|
||||
local (\(v,s,d) -> (v, M.insert name val s, d)) prog
|
||||
getvar name = liftM (! name) $ asks (\(v,s,d) -> s)
|
||||
|
||||
input = asks (\(v,s,d) -> v)
|
||||
|
||||
collect :: JQ a -> JQ [a]
|
||||
collect prog = liftM (map fst) $ collectPaths prog
|
||||
|
||||
collectPaths :: JQ a -> JQ [(a,Path)]
|
||||
collectPaths prog = do
|
||||
rd <- ask
|
||||
return $ runWriterT $ runReaderT prog rd
|
||||
|
||||
withDefn :: (String, [String], Filter) -> Filter -> Filter
|
||||
withDefn (name, formals, body) subexp =
|
||||
local (\(v,s,d) -> (v,s,M.insert (name,length formals) (Operator func) d)) subexp
|
||||
where
|
||||
func args = local (\(v,s,d) -> (v,M.empty,M.fromList (zip (zip formals (repeat 0)) (map (Operator . const) args)) `M.union` d)) body
|
||||
|
||||
subexp :: JQ a -> JQ a
|
||||
subexp = censor $ const []
|
||||
|
||||
|
||||
yieldPaths :: [(a,Path)] -> JQ a
|
||||
yieldPaths ps = ReaderT $ const $ WriterT ps
|
||||
|
||||
|
||||
insert :: JQ JSValue -> JSValue -> Path -> JQ JSValue
|
||||
insert replace base [] = replace
|
||||
insert replace (JSArray values) ((Left n):rest) = do
|
||||
let array = take (max (n+1) (length values)) (values ++ repeat JSNull)
|
||||
replacement <- insert replace (array !! n) rest
|
||||
let (left, (_:right)) = splitAt n array
|
||||
return $ JSArray $ left ++ [replacement] ++ right
|
||||
insert replace (JSObject obj) ((Right k):rest) = do
|
||||
let oldval = maybe JSNull id (lookup k $ fromJSObject obj)
|
||||
replacement <- insert replace oldval rest
|
||||
let withoutK = filter ((/= k) . fst) $ fromJSObject obj
|
||||
return $ JSObject $ toJSObject $ (k, replacement):withoutK
|
||||
|
||||
insert replace JSNull p@((Right k):rest) = insert replace (JSObject $ toJSObject []) p
|
||||
insert replace JSNull p@((Left n):rest) = insert replace (JSArray []) p
|
||||
insert _ base p = error $ "Cannot insert into " ++ intercalate ", " (map (either show show) p) ++ " of " ++ encode base
|
||||
|
||||
|
||||
eqj a b = JSBool $ a == b
|
||||
|
||||
boolj (JSBool false) = False
|
||||
boolj (JSNull) = False
|
||||
boolj _ = True
|
||||
|
||||
andj a b = JSBool $ boolj a && boolj b
|
||||
orj a b = JSBool $ boolj a || boolj b
|
||||
|
||||
liftp :: (JSValue -> JSValue) -> JQ JSValue
|
||||
liftp f = liftM f input
|
||||
|
||||
idp = undefined
|
||||
failp t = []
|
||||
|
||||
constp :: JSValue -> Program
|
||||
constp t t' = idp t
|
||||
|
||||
anyj :: [JSValue] -> Bool
|
||||
anyj values = any isTrue values
|
||||
where
|
||||
isTrue (JSBool False) = False
|
||||
isTrue (JSNull) = False
|
||||
isTrue _ = True
|
||||
|
||||
selectp prog = do
|
||||
match <- collect prog
|
||||
guard $ anyj match
|
||||
input
|
||||
|
||||
constStr :: String -> JQ JSValue
|
||||
constStr = return . JSString . toJSString
|
||||
|
||||
constInt :: Int -> JQ JSValue
|
||||
constInt = return . JSRational False . toRational
|
||||
|
||||
tr x = trace (show x) x
|
||||
|
||||
|
||||
assignp sel replace = do
|
||||
paths <- collectPaths sel
|
||||
t <- input
|
||||
foldM (\base (val,path) -> insert (return val >| replace) base path) t paths
|
||||
|
||||
arrayp prog = liftM JSArray $ collect prog
|
||||
|
||||
|
||||
childp' :: JSValue -> JQ JSValue
|
||||
childp' (JSArray values) = msum [tell [Left i] >> return v | (v,i) <- zip values [0..]]
|
||||
childp' (JSObject obj) = msum [tell [Right k] >> return v | (k,v) <- fromJSObject obj]
|
||||
childp' _ = mzero
|
||||
|
||||
childp = input >>= childp'
|
||||
|
||||
--findp :: Program -> Program
|
||||
findp prog = do
|
||||
found <- collect prog
|
||||
if anyj found then input else childp >| findp prog
|
||||
|
||||
groupp prog = do
|
||||
list <- input
|
||||
case list of
|
||||
JSArray values -> do
|
||||
marked <- forM values $ \v -> do
|
||||
m <- collect (return v >| prog)
|
||||
return (m,v)
|
||||
msum $
|
||||
map (return . JSArray . map snd) $
|
||||
groupBy ((==) `on` fst) $
|
||||
sortBy (comparing fst) $
|
||||
marked
|
||||
_ -> return JSNull
|
||||
|
||||
recp prog = do
|
||||
found <- collectPaths prog
|
||||
let (roots,subs) = partition (null . snd) found
|
||||
msum $
|
||||
[tell p >> return x | (x,p) <- roots] ++
|
||||
[tell p >> (return x >| recp prog) | (x,p) <- subs]
|
||||
|
||||
elsep p1 p2 = do
|
||||
p1' <- collectPaths p1
|
||||
if null p1' then p2 else yieldPaths p1'
|
||||
|
||||
fullresultp prog = do
|
||||
res <- collectPaths prog
|
||||
msum [return $ JSObject $ toJSObject $ [("val",a),("path",JSArray $ map fromPath p)] | (a,p) <- res]
|
||||
where
|
||||
fromPath (Left n) = js n
|
||||
fromPath (Right s) = js s
|
||||
|
||||
|
||||
withArray f (JSArray values) = JSArray $ f values
|
||||
withArray f x = x
|
||||
|
||||
withString f (JSString str) = JSString $ toJSString $ f $ fromJSString str
|
||||
withString f x = x
|
||||
|
||||
{-
|
||||
callp "select" [p] = selectp p
|
||||
callp "find" [p] = findp p
|
||||
callp "sort" [] = liftp (withArray sort)
|
||||
callp "group" [p] = groupp p
|
||||
callp "rec" [p] = recp p
|
||||
callp "empty" [] = mzero
|
||||
callp "true" [] = return $ JSBool True
|
||||
callp "false" [] = return $ JSBool False
|
||||
callp "null" [] = return $ JSNull
|
||||
callp "count" [] = liftp countj
|
||||
callp "fullresult" [p] = fullresultp p
|
||||
callp "uppercase" [] = liftp $ withString $ map toUpper
|
||||
callp "lowercase" [] = liftp $ withString $ map toLower
|
||||
-}
|
||||
|
||||
primitives = M.fromList [((name,arglen),Operator func) |
|
||||
(name,arglen,func) <- prim]
|
||||
where
|
||||
prim = [("if",1,\[p] -> selectp p),
|
||||
("find", 1, \[p] -> findp p),
|
||||
("group", 1, \[p] -> groupp p),
|
||||
("rec", 1, \[p] -> recp p),
|
||||
("true", 0, const $ return $ JSBool True),
|
||||
("false", 0, const $ return $ JSBool False),
|
||||
("null", 0, const $ return $ JSNull),
|
||||
("count", 0, const $ liftp countj),
|
||||
("fullresult", 1, \[p] -> fullresultp p),
|
||||
("zip", 0, const $ liftp zipj),
|
||||
("keys", 0, const $ liftp keysj)
|
||||
]
|
||||
|
||||
callp :: String -> [Filter] -> Filter
|
||||
|
||||
callp name args = do
|
||||
(v,s,d) <- ask
|
||||
runOperator (d ! (name, length args)) args
|
||||
|
||||
countj (JSArray v) = js$ length v
|
||||
countj (JSObject o) = js$ length $ fromJSObject o
|
||||
countj _ = js$ (1::Int)
|
||||
|
||||
lookupj :: JSValue -> JSValue -> JQ JSValue
|
||||
lookupj (JSArray values) (JSRational _ n) = do
|
||||
let idx = round n
|
||||
tell [Left idx]
|
||||
if idx >= 0 && idx < length values
|
||||
then return $ values !! idx
|
||||
else return $ JSNull
|
||||
lookupj (JSObject obj) (JSString s) = do
|
||||
tell [Right (fromJSString s)]
|
||||
case (lookup (fromJSString s) (fromJSObject obj)) of
|
||||
Just x -> return x
|
||||
Nothing -> return JSNull
|
||||
lookupj JSNull (JSRational _ n) = do
|
||||
tell [Left $ round n]
|
||||
return JSNull
|
||||
lookupj JSNull (JSString s) = do
|
||||
tell [Right (fromJSString s)]
|
||||
return JSNull
|
||||
--lookupj v i = error $ "Cannot get element " ++ encode i ++ " of " ++ encode v
|
||||
lookupj _ _ = mzero
|
||||
|
||||
|
||||
plusj (JSRational f1 n1) (JSRational f2 n2) = JSRational (f1 || f2) (n1 + n2)
|
||||
plusj (JSString s1) (JSString s2) = JSString $ toJSString (fromJSString s1 ++ fromJSString s2)
|
||||
plusj (JSArray a1) (JSArray a2) = JSArray $ a1 ++ a2
|
||||
plusj (JSObject o1) (JSObject o2) = JSObject $ toJSObject $ o1' ++ fromJSObject o2
|
||||
where
|
||||
newkeys = map fst $ fromJSObject o2
|
||||
o1' = filter (not . (`elem` newkeys) . fst) $ fromJSObject o1
|
||||
|
||||
keysj (JSArray v) = js [0..length v - 1]
|
||||
keysj (JSObject obj) = js (map fst $ fromJSObject obj)
|
||||
keysj _ = JSArray []
|
||||
|
||||
zipj jsonValue = result $ tx values ctor
|
||||
where
|
||||
(result, values) = extract packed
|
||||
(packed, ctor) = case jsonValue of
|
||||
JSArray values -> (values, (\vs' -> JSArray $ [v' | Just v' <- vs']))
|
||||
JSObject jsObject ->
|
||||
let object = fromJSObject jsObject
|
||||
keys = map fst object
|
||||
values = map snd object
|
||||
build vs' = [(k,v') | (k,Just v') <- zip keys vs']
|
||||
in (values, JSObject . toJSObject . build)
|
||||
|
||||
_ -> error "only arrays and objects may be zipped"
|
||||
|
||||
extract values | all isArray values = (JSArray, [map Just arr | JSArray arr <- values])
|
||||
| all isObject values =
|
||||
let objects = [fromJSObject o | JSObject o <- values]
|
||||
keys = S.toList $ S.fromList [k | obj <- objects, (k,_) <- obj]
|
||||
values' :: [[Maybe JSValue]]
|
||||
values' = [[lookup k object | k <- keys] | object <- objects]
|
||||
result r = JSObject $ toJSObject $ zip keys r
|
||||
in (result, values')
|
||||
| otherwise = error "elements of zipped value must be all objects or all arrays"
|
||||
where
|
||||
isArray (JSArray a) = True
|
||||
isArray _ = False
|
||||
isObject (JSObject o) = True
|
||||
isObject _ = False
|
||||
|
||||
head' [] = Nothing
|
||||
head' (x:xs) = x
|
||||
tail' [] = []
|
||||
tail' (x:xs) = xs
|
||||
tx values ctor | all null values = []
|
||||
| otherwise = ctor (map head' values):tx (map tail' values) ctor
|
||||
|
||||
js :: JSON a => a -> JSValue
|
||||
js = showJSON
|
||||
|
||||
index s = do
|
||||
v <- input
|
||||
lookupj v (js s)
|
||||
|
||||
|
||||
dictp progs = do
|
||||
liftM (JSObject . toJSObject) $ forM progs $ \(k,v) -> do
|
||||
JSString k' <- subexp k
|
||||
v' <- subexp v
|
||||
return (fromJSString k', v')
|
||||
|
||||
@@ -1,102 +0,0 @@
|
||||
{
|
||||
module Lexer where
|
||||
import Control.Monad.Error
|
||||
}
|
||||
|
||||
%wrapper "monadUserState"
|
||||
|
||||
$digit = 0-9
|
||||
$alpha = [a-zA-Z_]
|
||||
@reserved = "."|"["|"]"|","|":"|"("|")"|"{"|"}"|"|"|"=="|"+"|"="|"$"|"def"|";"|"else"|"and"|"or"|"as"
|
||||
@ident = $alpha [$alpha $digit]*
|
||||
@string = \" ($printable)* \"
|
||||
|
||||
|
||||
tokens :-
|
||||
|
||||
<0> "#" ($printable # [\n\r])* ;
|
||||
<0> $white+ ;
|
||||
<0> @reserved { tok TRes }
|
||||
<0> @ident { tok TIdent }
|
||||
<0> $digit+ { tok $ TInt . read }
|
||||
|
||||
|
||||
<0> \" { enterString }
|
||||
<string> \" { leaveString }
|
||||
<string> ($printable # [\"\\]) { pushString id }
|
||||
<string> \\ [\"\\\/] { pushString (drop 1) }
|
||||
<string> \\ [nrt] { pushString (escape . drop 1) }
|
||||
--<string> \\ 'u' [0-9a-fA-F]{4}
|
||||
-- { pushString (parseUnicode . drop 2) }
|
||||
|
||||
-- @string { \s -> TString $ init $ tail s}
|
||||
|
||||
|
||||
{
|
||||
|
||||
escape :: String -> String
|
||||
escape "r" = "\r"
|
||||
escape "n" = "\n"
|
||||
escape "t" = "\t"
|
||||
|
||||
getState :: Alex AlexState
|
||||
getState = Alex $ \s -> Right (s, s)
|
||||
|
||||
getUserState :: Alex AlexUserState
|
||||
getUserState = liftM alex_ust getState
|
||||
|
||||
setUserState :: AlexUserState -> Alex ()
|
||||
setUserState s' = Alex $ \s -> Right (s{alex_ust = s'}, ())
|
||||
|
||||
alexEOF = return $ Nothing
|
||||
|
||||
enterString input len = do
|
||||
setUserState []
|
||||
alexSetStartCode string
|
||||
skip input len
|
||||
|
||||
pushString f i@(p, _, s) len = do
|
||||
buf <- getUserState
|
||||
setUserState (buf ++ [f $ take len s])
|
||||
skip i len
|
||||
|
||||
leaveString input len = do
|
||||
s <- getUserState
|
||||
alexSetStartCode 0
|
||||
return $ Just $ TString $ concat s
|
||||
|
||||
|
||||
tok f (p,_,s) len = return $ Just $ f (take len s)
|
||||
data Token = TRes String | TString String | TIdent String | TInt Int
|
||||
|
||||
instance Show Token where
|
||||
show (TRes t) = "token " ++ t
|
||||
show (TString t) = "string " ++ t
|
||||
show (TIdent t) = "identifier " ++ t
|
||||
show (TInt t) = "integer " ++ show t
|
||||
|
||||
|
||||
type AlexUserState = [String]
|
||||
|
||||
alexInitUserState = undefined
|
||||
|
||||
wrapError (Alex scanner) = Alex $ \s -> case scanner s of
|
||||
Left message -> Left (message ++ " at " ++ showpos (alex_pos s))
|
||||
where
|
||||
showpos (AlexPn off line col) = "line " ++ show line ++ ", column " ++ show col
|
||||
x -> x
|
||||
|
||||
scanner = do
|
||||
tok <- wrapError alexMonadScan
|
||||
case tok of
|
||||
Nothing -> do
|
||||
s <- getState
|
||||
case alex_scd s of
|
||||
0 -> return []
|
||||
string -> alexError "Unterminated string literal"
|
||||
Just tok -> liftM (tok:) scanner
|
||||
|
||||
runLexer :: String -> Either String [Token]
|
||||
runLexer input = runAlex input scanner
|
||||
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
import Parser
|
||||
import Lexer
|
||||
import JQ
|
||||
import Text.JSON
|
||||
import Text.JSON.String
|
||||
import PrettyJSON
|
||||
import System.Environment
|
||||
import Control.Monad
|
||||
import System.IO
|
||||
|
||||
parseJS :: String -> JSValue
|
||||
parseJS s = case runGetJSON readJSValue s of
|
||||
Left err -> error err
|
||||
Right val -> val
|
||||
|
||||
|
||||
main = do
|
||||
[program] <- getArgs
|
||||
stdlib <- openFile "stdlib.jq" ReadMode >>= hGetContents
|
||||
json <- liftM parseJS $ hGetContents stdin
|
||||
case runLexer (stdlib ++ program) >>= runParser of
|
||||
Left err -> putStrLn err
|
||||
Right program -> mapM_ (putStrLn . show . renderJSON) (runJQ program json)
|
||||
@@ -1,8 +1,102 @@
|
||||
jq: *.hs Parser.hs Lexer.hs
|
||||
ghc *.hs -o jq
|
||||
CC=gcc
|
||||
CFLAGS=-Wextra -Wall -Wno-missing-field-initializers -Wno-unused-parameter -std=gnu99 -ggdb -Wno-unused-function $(EXTRA_CFLAGS)
|
||||
|
||||
Parser.hs: Parser.y
|
||||
happy -i Parser.y
|
||||
prefix=/usr/local
|
||||
mandir=$(prefix)/share/man
|
||||
|
||||
Lexer.hs: Lexer.x
|
||||
alex Lexer.x
|
||||
|
||||
.PHONY: all clean releasedep tarball install uninstall test releasetag
|
||||
all: jq
|
||||
|
||||
lexer.gen.c: lexer.l
|
||||
flex -o lexer.gen.c --header-file=lexer.gen.h lexer.l
|
||||
lexer.gen.h: lexer.gen.c
|
||||
|
||||
parser.gen.c: parser.y lexer.gen.h
|
||||
bison -W -d parser.y -v --report-file=parser.gen.info -o $@
|
||||
parser.gen.h: parser.gen.c
|
||||
|
||||
jv_utf8_tables.gen.h: gen_utf8_tables.py
|
||||
python $^ > $@
|
||||
jv_unicode.c: jv_utf8_tables.gen.h
|
||||
|
||||
version.gen.h: VERSION
|
||||
sed 's/.*/#define JQ_VERSION "&"/' $^ > $@
|
||||
main.c: version.gen.h
|
||||
|
||||
JQ_SRC=parser.gen.c lexer.gen.c opcode.c bytecode.c compile.c execute.c builtin.c jv.c jv_parse.c jv_print.c jv_dtoa.c jv_unicode.c jv_aux.c jv_alloc.c
|
||||
|
||||
jq_test: CFLAGS += -DJQ_DEBUG=1
|
||||
jq_test: $(JQ_SRC) jq_test.c
|
||||
$(CC) $(CFLAGS) $(CFLAGS_DEBUG) -o $@ $^
|
||||
|
||||
jq: CFLAGS += -O -DJQ_DEBUG=0
|
||||
jq: $(JQ_SRC) main.c
|
||||
$(CC) $(CFLAGS) $(CFLAGS_OPT) -o $@ $^
|
||||
|
||||
test: jq_test
|
||||
valgrind --error-exitcode=1 -q --leak-check=full ./jq_test >/dev/null
|
||||
|
||||
BINARIES=jq jq_test
|
||||
PLATFORMS=linux32 linux64 osx32 osx64 win32 win64
|
||||
|
||||
build/linux32%: CC='x86_64-linux-gnu-gcc -m32'
|
||||
build/linux64%: CC='x86_64-linux-gnu-gcc -m64'
|
||||
|
||||
# OS X cross compilers can be gotten from
|
||||
# https://launchpad.net/~flosoft/+archive/cross-apple
|
||||
build/osx32%: CC='i686-apple-darwin10-gcc -m32'
|
||||
build/osx64%: CC='i686-apple-darwin10-gcc -m64'
|
||||
|
||||
# On Debian, you can get windows compilers in the
|
||||
# gcc-mingw-w64-i686 and gcc-mingw-w64-x86-64 packages.
|
||||
build/win32%: CC='i686-w64-mingw32-gcc -m32' EXTRA_CFLAGS=-DJQ_DEFAULT_ENABLE_COLOR=0
|
||||
build/win64%: CC='x86_64-w64-mingw32-gcc -m64' EXTRA_CFLAGS=-DJQ_DEFAULT_ENABLE_COLOR=0
|
||||
|
||||
ALL_BINARIES=$(foreach platform, $(PLATFORMS), $(foreach binary, $(BINARIES), build/$(platform)/$(binary)))
|
||||
|
||||
$(ALL_BINARIES): build/%:
|
||||
mkdir -p $(@D)
|
||||
make -B $(BINARIES) CC=$(CC)
|
||||
cp $(BINARIES) $(@D)
|
||||
|
||||
binaries: $(ALL_BINARIES)
|
||||
|
||||
clean:
|
||||
rm -rf build
|
||||
rm -f $(BINARIES) *.gen.*
|
||||
|
||||
releasedep: lexer.gen.c parser.gen.c jv_utf8_tables.gen.h
|
||||
|
||||
releasetag:
|
||||
git tag -s "jq-$$(cat VERSION)" -m "jq release $$(cat VERSION)"
|
||||
|
||||
docs/content/2.download/source/jq.tgz: jq
|
||||
mkdir -p `dirname $@`
|
||||
tar -czvf $@ `git ls-files; ls *.gen.*`
|
||||
|
||||
tarball: docs/content/2.download/source/jq.tgz
|
||||
|
||||
jq.1: docs/content/3.manual/manual.yml
|
||||
( cd docs; rake manpage; ) > $@
|
||||
|
||||
install: jq jq.1
|
||||
install -d -m 0755 $(prefix)/bin
|
||||
install -m 0755 jq $(prefix)/bin
|
||||
install -d -m 0755 $(mandir)/man1
|
||||
install -m 0755 jq.1 $(mandir)/man1
|
||||
|
||||
uninstall:
|
||||
rm -vf $(prefix)/bin/jq
|
||||
rm -vf $(mandir)/man1/jq.1
|
||||
|
||||
|
||||
www: docs/output www_binaries
|
||||
|
||||
docs/output:
|
||||
cd docs; rake build
|
||||
|
||||
www_binaries: docs/output binaries
|
||||
$(foreach platform, $(PLATFORMS), $(foreach binary, $(BINARIES), \
|
||||
mkdir -p docs/output/download/$(platform); \
|
||||
cp build/$(platform)/$(binary) docs/output/download/$(platform)/$(binary); ))
|
||||
@@ -1,121 +0,0 @@
|
||||
{
|
||||
module Parser where
|
||||
import Lexer
|
||||
import JQ
|
||||
import Text.JSON
|
||||
import Debug.Trace
|
||||
import Data.List
|
||||
import Control.Monad.Error
|
||||
import Control.Monad.Reader
|
||||
|
||||
instance Error (Maybe a) where
|
||||
noMsg = Nothing
|
||||
strMsg = const Nothing
|
||||
|
||||
instance (Error a, Error b) => Error (a, b) where
|
||||
noMsg = (noMsg, noMsg)
|
||||
strMsg s = (strMsg s, strMsg s)
|
||||
}
|
||||
|
||||
%name runParser TopLevel
|
||||
%tokentype { Token }
|
||||
|
||||
%monad { Either String }
|
||||
%error { \t -> throwError $ "parse error: unexpected " ++ if null t then [] else (show $ t) }
|
||||
|
||||
%token
|
||||
'|' { TRes "|" }
|
||||
'.' { TRes "." }
|
||||
'[' { TRes "[" }
|
||||
']' { TRes "]" }
|
||||
'{' { TRes "{" }
|
||||
'}' { TRes "}" }
|
||||
'(' { TRes "(" }
|
||||
')' { TRes ")" }
|
||||
'$' { TRes "$" }
|
||||
'as' { TRes "as" }
|
||||
',' { TRes "," }
|
||||
':' { TRes ":" }
|
||||
'==' { TRes "==" }
|
||||
'=' { TRes "=" }
|
||||
'+' { TRes "+" }
|
||||
'def' { TRes "def" }
|
||||
';' { TRes ";" }
|
||||
'else' { TRes "else" }
|
||||
'and' { TRes "and" }
|
||||
'or' { TRes "or" }
|
||||
Ident { TIdent $$ }
|
||||
String { TString $$ }
|
||||
Int { TInt $$ }
|
||||
|
||||
|
||||
%left 'else'
|
||||
%right '|'
|
||||
%left '='
|
||||
%left ','
|
||||
%left 'and' 'or'
|
||||
%nonassoc '=='
|
||||
%left '+'
|
||||
|
||||
%%
|
||||
|
||||
TopLevel
|
||||
: Defn TopLevel { withDefn $1 $2 }
|
||||
| Exp { $1 }
|
||||
|
||||
Exp
|
||||
: Exp 'else' Exp { $1 `elsep` $3 }
|
||||
| Assign '|' Exp { do { v <- snd $1; setvar (fst $1) v $3 } }
|
||||
| Exp '|' Exp { $1 >| $3 }
|
||||
| Exp ',' Exp { $1 `mplus` $3 }
|
||||
| Exp 'and' Exp { liftM2 andj $1 $3 }
|
||||
| Exp 'or' Exp { liftM2 orj $1 $3 }
|
||||
| Exp '=' Exp { assignp $1 $3 }
|
||||
| Exp '==' Exp { liftM2 eqj (subexp $1) (subexp $3) }
|
||||
| Exp '+' Exp { liftM2 plusj (subexp $1) (subexp $3) }
|
||||
| Term { $1 }
|
||||
|
||||
Assign
|
||||
: Term 'as' '$' Ident { ($4, $1) }
|
||||
|
||||
Defn
|
||||
: 'def' Ident '=' Exp ';' { ($2, [], $4) }
|
||||
| 'def' Ident '(' ParamList ')' '=' Exp ';' { ($2, $4, $7) }
|
||||
|
||||
ParamList
|
||||
: { [] }
|
||||
| Ident { [$1] }
|
||||
| Ident ';' ParamList { $1:$3 }
|
||||
|
||||
ExpD
|
||||
: ExpD '|' ExpD { $1 >| $3 }
|
||||
| ExpD '==' ExpD { liftM2 eqj $1 $3 }
|
||||
| Term { $1 }
|
||||
|
||||
|
||||
Term
|
||||
: '.' { input }
|
||||
| Term '.' Ident { $1 >| index $3 }
|
||||
| '.' Ident { index $2 }
|
||||
| String { constStr $1 }
|
||||
| Term '[' Exp ']' { do {t <- $1; i <- subexp $3; lookupj t i} }
|
||||
| Term '[' ']' { $1 >| childp }
|
||||
| '(' Exp ')' { $2 }
|
||||
| '[' Exp ']' { arrayp $2 }
|
||||
| '[' ']' { arrayp (callp "empty" []) }
|
||||
| Int { constInt $1 }
|
||||
| '{' MkDict '}' { dictp $2 }
|
||||
| Ident '(' Exp ')' { callp $1 [$3] }
|
||||
| Ident { callp $1 [] }
|
||||
| '$' Ident { getvar $2 }
|
||||
|
||||
MkDict
|
||||
: { [] }
|
||||
| MkDictPair { [$1] }
|
||||
| MkDictPair ',' MkDict { $1:$3 }
|
||||
|
||||
MkDictPair
|
||||
: Ident ':' ExpD { (constStr $1, $3) }
|
||||
| Ident { (constStr $1, index $1) }
|
||||
| String ':' ExpD { (constStr $1, $3) }
|
||||
| '(' Exp ')' ':' ExpD{ ($2, $5) }
|
||||
@@ -1,12 +0,0 @@
|
||||
module PrettyJSON where
|
||||
import Text.JSON
|
||||
import Text.PrettyPrint
|
||||
|
||||
renderJSON (JSArray vals) = brackets $ fsep $ punctuate comma $ map renderJSON vals
|
||||
|
||||
renderJSON (JSObject jsObject) =
|
||||
let object = fromJSObject jsObject
|
||||
in braces $ fsep $ punctuate comma $
|
||||
[hang (renderJSON (JSString $ toJSString $ k) <> colon) 2 (renderJSON v)
|
||||
| (k,v) <- object]
|
||||
renderJSON x = text $ encode x
|
||||
@@ -0,0 +1,14 @@
|
||||
jq
|
||||
==
|
||||
|
||||
jq is a command-line JSON processor.
|
||||
|
||||
If you want to learn to use jq, read the documentation at
|
||||
[http://stedolan.github.com/jq](http://stedolan.github.com/jq). This
|
||||
documentation is generated from the docs/ folder of this repository.
|
||||
|
||||
If you want to hack on jq, feel free, but be warned that its internals
|
||||
are not well-documented at the moment. Bring a hard hat and a shovel.
|
||||
|
||||
You can find some basic build instructions at
|
||||
[http://stedolan.github.com/jq/download](http://stedolan.github.com/jq/download)
|
||||
@@ -0,0 +1,382 @@
|
||||
#include <string.h>
|
||||
#include "builtin.h"
|
||||
#include "bytecode.h"
|
||||
#include "compile.h"
|
||||
#include "parser.h"
|
||||
#include "locfile.h"
|
||||
#include "jv_aux.h"
|
||||
|
||||
|
||||
typedef jv (*func_1)(jv);
|
||||
typedef jv (*func_2)(jv,jv);
|
||||
typedef jv (*func_3)(jv,jv,jv);
|
||||
typedef jv (*func_4)(jv,jv,jv,jv);
|
||||
typedef jv (*func_5)(jv,jv,jv,jv,jv);
|
||||
jv cfunction_invoke(struct cfunction* function, jv input[]) {
|
||||
switch (function->nargs) {
|
||||
case 1: return ((func_1)function->fptr)(input[0]);
|
||||
case 2: return ((func_2)function->fptr)(input[0], input[1]);
|
||||
case 3: return ((func_3)function->fptr)(input[0], input[1], input[2]);
|
||||
case 4: return ((func_4)function->fptr)(input[0], input[1], input[2], input[3]);
|
||||
case 5: return ((func_5)function->fptr)(input[0], input[1], input[2], input[3], input[4]);
|
||||
default: return jv_invalid_with_msg(jv_string("Function takes too many arguments"));
|
||||
}
|
||||
}
|
||||
|
||||
static jv type_error(jv bad, const char* msg) {
|
||||
jv err = jv_invalid_with_msg(jv_string_fmt("%s %s",
|
||||
jv_kind_name(jv_get_kind(bad)),
|
||||
msg));
|
||||
jv_free(bad);
|
||||
return err;
|
||||
}
|
||||
|
||||
static jv type_error2(jv bad1, jv bad2, const char* msg) {
|
||||
jv err = jv_invalid_with_msg(jv_string_fmt("%s and %s %s",
|
||||
jv_kind_name(jv_get_kind(bad1)),
|
||||
jv_kind_name(jv_get_kind(bad2)),
|
||||
msg));
|
||||
jv_free(bad1);
|
||||
jv_free(bad2);
|
||||
return err;
|
||||
}
|
||||
|
||||
static jv f_plus(jv input, jv a, jv b) {
|
||||
jv_free(input);
|
||||
if (jv_get_kind(a) == JV_KIND_NUMBER && jv_get_kind(b) == JV_KIND_NUMBER) {
|
||||
return jv_number(jv_number_value(a) +
|
||||
jv_number_value(b));
|
||||
} else if (jv_get_kind(a) == JV_KIND_STRING && jv_get_kind(b) == JV_KIND_STRING) {
|
||||
return jv_string_concat(a, b);
|
||||
} else if (jv_get_kind(a) == JV_KIND_ARRAY && jv_get_kind(b) == JV_KIND_ARRAY) {
|
||||
return jv_array_concat(a, b);
|
||||
} else if (jv_get_kind(a) == JV_KIND_OBJECT && jv_get_kind(b) == JV_KIND_OBJECT) {
|
||||
return jv_object_merge(a, b);
|
||||
} else {
|
||||
return type_error2(a, b, "cannot be added");
|
||||
}
|
||||
}
|
||||
|
||||
static jv f_minus(jv input, jv a, jv b) {
|
||||
jv_free(input);
|
||||
if (jv_get_kind(a) == JV_KIND_NUMBER && jv_get_kind(b) == JV_KIND_NUMBER) {
|
||||
return jv_number(jv_number_value(a) - jv_number_value(b));
|
||||
} else if (jv_get_kind(a) == JV_KIND_ARRAY && jv_get_kind(b) == JV_KIND_ARRAY) {
|
||||
jv out = jv_array();
|
||||
for (int i=0; i<jv_array_length(jv_copy(a)); i++) {
|
||||
jv x = jv_array_get(jv_copy(a), i);
|
||||
int include = 1;
|
||||
for (int j=0; j<jv_array_length(jv_copy(b)); j++) {
|
||||
if (jv_equal(jv_copy(x), jv_array_get(jv_copy(b), j))) {
|
||||
include = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (include)
|
||||
out = jv_array_append(out, jv_copy(x));
|
||||
jv_free(x);
|
||||
}
|
||||
jv_free(a);
|
||||
jv_free(b);
|
||||
return out;
|
||||
} else {
|
||||
return type_error2(a, b, "cannot be subtracted");
|
||||
}
|
||||
}
|
||||
|
||||
static jv f_multiply(jv input, jv a, jv b) {
|
||||
jv_free(input);
|
||||
if (jv_get_kind(a) == JV_KIND_NUMBER && jv_get_kind(b) == JV_KIND_NUMBER) {
|
||||
return jv_number(jv_number_value(a) * jv_number_value(b));
|
||||
} else {
|
||||
return type_error2(a, b, "cannot be multiplied");
|
||||
}
|
||||
}
|
||||
|
||||
static jv f_divide(jv input, jv a, jv b) {
|
||||
jv_free(input);
|
||||
if (jv_get_kind(a) == JV_KIND_NUMBER && jv_get_kind(b) == JV_KIND_NUMBER) {
|
||||
return jv_number(jv_number_value(a) / jv_number_value(b));
|
||||
} else {
|
||||
return type_error2(a, b, "cannot be divided");
|
||||
}
|
||||
}
|
||||
|
||||
static jv f_add(jv array) {
|
||||
if (jv_get_kind(array) != JV_KIND_ARRAY) {
|
||||
return type_error(array, "cannot have its elements added");
|
||||
} else if (jv_array_length(jv_copy(array)) == 0) {
|
||||
jv_free(array);
|
||||
return jv_null();
|
||||
} else {
|
||||
jv sum = jv_array_get(jv_copy(array), 0);
|
||||
for (int i = 1; i < jv_array_length(jv_copy(array)); i++) {
|
||||
sum = f_plus(jv_null(), sum, jv_array_get(jv_copy(array), i));
|
||||
}
|
||||
jv_free(array);
|
||||
return sum;
|
||||
}
|
||||
}
|
||||
|
||||
static jv f_equal(jv input, jv a, jv b) {
|
||||
jv_free(input);
|
||||
return jv_bool(jv_equal(a, b));
|
||||
}
|
||||
|
||||
static jv f_notequal(jv input, jv a, jv b) {
|
||||
jv_free(input);
|
||||
return jv_bool(!jv_equal(a, b));
|
||||
}
|
||||
|
||||
enum cmp_op {
|
||||
CMP_OP_LESS,
|
||||
CMP_OP_GREATER,
|
||||
CMP_OP_LESSEQ,
|
||||
CMP_OP_GREATEREQ
|
||||
};
|
||||
|
||||
static jv order_cmp(jv input, jv a, jv b, enum cmp_op op) {
|
||||
jv_free(input);
|
||||
int r = jv_cmp(a, b);
|
||||
return jv_bool((op == CMP_OP_LESS && r < 0) ||
|
||||
(op == CMP_OP_LESSEQ && r <= 0) ||
|
||||
(op == CMP_OP_GREATEREQ && r >= 0) ||
|
||||
(op == CMP_OP_GREATER && r > 0));
|
||||
}
|
||||
|
||||
static jv f_less(jv input, jv a, jv b) {
|
||||
return order_cmp(input, a, b, CMP_OP_LESS);
|
||||
}
|
||||
|
||||
static jv f_greater(jv input, jv a, jv b) {
|
||||
return order_cmp(input, a, b, CMP_OP_GREATER);
|
||||
}
|
||||
|
||||
static jv f_lesseq(jv input, jv a, jv b) {
|
||||
return order_cmp(input, a, b, CMP_OP_LESSEQ);
|
||||
}
|
||||
|
||||
static jv f_greatereq(jv input, jv a, jv b) {
|
||||
return order_cmp(input, a, b, CMP_OP_GREATEREQ);
|
||||
}
|
||||
|
||||
static jv f_contains(jv a, jv b) {
|
||||
jv_kind akind = jv_get_kind(a);
|
||||
|
||||
if (akind == jv_get_kind(b)) {
|
||||
return jv_bool(jv_contains(a, b));
|
||||
} else {
|
||||
return type_error2(a, b, "cannot have their containment checked");
|
||||
}
|
||||
}
|
||||
|
||||
static jv f_tonumber(jv input) {
|
||||
if (jv_get_kind(input) == JV_KIND_NUMBER) {
|
||||
return input;
|
||||
}
|
||||
if (jv_get_kind(input) == JV_KIND_STRING) {
|
||||
jv parsed = jv_parse(jv_string_value(input));
|
||||
if (!jv_is_valid(parsed) || jv_get_kind(parsed) == JV_KIND_NUMBER) {
|
||||
jv_free(input);
|
||||
return parsed;
|
||||
}
|
||||
}
|
||||
return type_error(input, "cannot be parsed as a number");
|
||||
}
|
||||
|
||||
static jv f_length(jv input) {
|
||||
if (jv_get_kind(input) == JV_KIND_ARRAY) {
|
||||
return jv_number(jv_array_length(input));
|
||||
} else if (jv_get_kind(input) == JV_KIND_OBJECT) {
|
||||
return jv_number(jv_object_length(input));
|
||||
} else if (jv_get_kind(input) == JV_KIND_STRING) {
|
||||
return jv_number(jv_string_length(input));
|
||||
} else if (jv_get_kind(input) == JV_KIND_NULL) {
|
||||
jv_free(input);
|
||||
return jv_number(0);
|
||||
} else {
|
||||
return type_error(input, "has no length");
|
||||
}
|
||||
}
|
||||
|
||||
static jv f_tostring(jv input) {
|
||||
if (jv_get_kind(input) == JV_KIND_STRING) {
|
||||
return input;
|
||||
} else {
|
||||
return jv_dump_string(input, 0);
|
||||
}
|
||||
}
|
||||
|
||||
static jv f_keys(jv input) {
|
||||
if (jv_get_kind(input) == JV_KIND_OBJECT || jv_get_kind(input) == JV_KIND_ARRAY) {
|
||||
return jv_keys(input);
|
||||
} else {
|
||||
return type_error(input, "has no keys");
|
||||
}
|
||||
}
|
||||
|
||||
static jv f_sort(jv input){
|
||||
if (jv_get_kind(input) == JV_KIND_ARRAY) {
|
||||
return jv_sort(input, jv_copy(input));
|
||||
} else {
|
||||
return type_error(input, "cannot be sorted, as it is not an array");
|
||||
}
|
||||
}
|
||||
|
||||
static jv f_sort_by_impl(jv input, jv keys) {
|
||||
if (jv_get_kind(input) == JV_KIND_ARRAY &&
|
||||
jv_get_kind(keys) == JV_KIND_ARRAY &&
|
||||
jv_array_length(jv_copy(input)) == jv_array_length(jv_copy(keys))) {
|
||||
return jv_sort(input, keys);
|
||||
} else {
|
||||
return type_error2(input, keys, "cannot be sorted, as they are not both arrays");
|
||||
}
|
||||
}
|
||||
|
||||
static jv f_group_by_impl(jv input, jv keys) {
|
||||
if (jv_get_kind(input) == JV_KIND_ARRAY &&
|
||||
jv_get_kind(keys) == JV_KIND_ARRAY &&
|
||||
jv_array_length(jv_copy(input)) == jv_array_length(jv_copy(keys))) {
|
||||
return jv_group(input, keys);
|
||||
} else {
|
||||
return type_error2(input, keys, "cannot be sorted, as they are not both arrays");
|
||||
}
|
||||
}
|
||||
|
||||
static jv minmax_by(jv values, jv keys, int is_min) {
|
||||
if (jv_get_kind(values) != JV_KIND_ARRAY)
|
||||
return type_error2(values, keys, "cannot be iterated over");
|
||||
if (jv_get_kind(keys) != JV_KIND_ARRAY)
|
||||
return type_error2(values, keys, "cannot be iterated over");
|
||||
if (jv_array_length(jv_copy(values)) != jv_array_length(jv_copy(keys)))
|
||||
return type_error2(values, keys, "have wrong length");
|
||||
|
||||
if (jv_array_length(jv_copy(values)) == 0) {
|
||||
jv_free(values);
|
||||
jv_free(keys);
|
||||
return jv_null();
|
||||
}
|
||||
jv ret = jv_array_get(jv_copy(values), 0);
|
||||
jv retkey = jv_array_get(jv_copy(keys), 0);
|
||||
for (int i=1; i<jv_array_length(jv_copy(values)); i++) {
|
||||
jv item = jv_array_get(jv_copy(keys), i);
|
||||
int cmp = jv_cmp(jv_copy(item), jv_copy(retkey));
|
||||
if ((cmp < 0) == (is_min == 1)) {
|
||||
jv_free(retkey);
|
||||
retkey = item;
|
||||
jv_free(ret);
|
||||
ret = jv_array_get(jv_copy(values), i);
|
||||
} else {
|
||||
jv_free(item);
|
||||
}
|
||||
}
|
||||
jv_free(values);
|
||||
jv_free(keys);
|
||||
jv_free(retkey);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static jv f_min(jv x) {
|
||||
return minmax_by(x, jv_copy(x), 1);
|
||||
}
|
||||
|
||||
static jv f_max(jv x) {
|
||||
return minmax_by(x, jv_copy(x), 0);
|
||||
}
|
||||
|
||||
static jv f_min_by_impl(jv x, jv y) {
|
||||
return minmax_by(x, y, 1);
|
||||
}
|
||||
|
||||
static jv f_max_by_impl(jv x, jv y) {
|
||||
return minmax_by(x, y, 0);
|
||||
}
|
||||
|
||||
|
||||
static jv f_type(jv input) {
|
||||
jv out = jv_string(jv_kind_name(jv_get_kind(input)));
|
||||
jv_free(input);
|
||||
return out;
|
||||
}
|
||||
|
||||
static jv f_error(jv input, jv msg) {
|
||||
jv_free(input);
|
||||
msg = f_tostring(msg);
|
||||
return jv_invalid_with_msg(msg);
|
||||
}
|
||||
|
||||
|
||||
static struct cfunction function_list[] = {
|
||||
{(cfunction_ptr)f_plus, "_plus", 3},
|
||||
{(cfunction_ptr)f_minus, "_minus", 3},
|
||||
{(cfunction_ptr)f_multiply, "_multiply", 3},
|
||||
{(cfunction_ptr)f_divide, "_divide", 3},
|
||||
{(cfunction_ptr)f_tonumber, "tonumber", 1},
|
||||
{(cfunction_ptr)f_tostring, "tostring", 1},
|
||||
{(cfunction_ptr)f_keys, "keys", 1},
|
||||
{(cfunction_ptr)f_equal, "_equal", 3},
|
||||
{(cfunction_ptr)f_notequal, "_notequal", 3},
|
||||
{(cfunction_ptr)f_less, "_less", 3},
|
||||
{(cfunction_ptr)f_greater, "_greater", 3},
|
||||
{(cfunction_ptr)f_lesseq, "_lesseq", 3},
|
||||
{(cfunction_ptr)f_greatereq, "_greatereq", 3},
|
||||
{(cfunction_ptr)f_contains, "contains", 2},
|
||||
{(cfunction_ptr)f_length, "length", 1},
|
||||
{(cfunction_ptr)f_type, "type", 1},
|
||||
{(cfunction_ptr)f_add, "add", 1},
|
||||
{(cfunction_ptr)f_sort, "sort", 1},
|
||||
{(cfunction_ptr)f_sort_by_impl, "_sort_by_impl", 2},
|
||||
{(cfunction_ptr)f_group_by_impl, "_group_by_impl", 2},
|
||||
{(cfunction_ptr)f_min, "min", 1},
|
||||
{(cfunction_ptr)f_max, "max", 1},
|
||||
{(cfunction_ptr)f_min_by_impl, "_min_by_impl", 2},
|
||||
{(cfunction_ptr)f_max_by_impl, "_max_by_impl", 2},
|
||||
{(cfunction_ptr)f_error, "error", 2},
|
||||
};
|
||||
|
||||
static struct symbol_table cbuiltins =
|
||||
{function_list, sizeof(function_list)/sizeof(function_list[0])};
|
||||
|
||||
typedef block (*bytecoded_builtin)();
|
||||
struct bytecoded_builtin { const char* name; block code; };
|
||||
static block bind_bytecoded_builtins(block b) {
|
||||
struct bytecoded_builtin builtin_defs[] = {
|
||||
{"empty", gen_op_simple(BACKTRACK)},
|
||||
{"false", gen_const(jv_false())},
|
||||
{"true", gen_const(jv_true())},
|
||||
{"null", gen_const(jv_null())},
|
||||
{"not", gen_condbranch(gen_const(jv_false()),
|
||||
gen_const(jv_true()))}
|
||||
};
|
||||
block builtins = gen_noop();
|
||||
for (unsigned i=0; i<sizeof(builtin_defs)/sizeof(builtin_defs[0]); i++) {
|
||||
builtins = BLOCK(builtins, gen_function(builtin_defs[i].name, gen_noop(),
|
||||
builtin_defs[i].code));
|
||||
}
|
||||
return block_bind(builtins, b, OP_IS_CALL_PSEUDO);
|
||||
}
|
||||
|
||||
static const char* jq_builtins[] = {
|
||||
"def map(f): [.[] | f];",
|
||||
"def select(f): if f then . else empty end;",
|
||||
"def sort_by(f): _sort_by_impl(map([f]));",
|
||||
"def group_by(f): _group_by_impl(map([f]));",
|
||||
"def unique: group_by(.) | map(.[0]);",
|
||||
"def max_by(f): _max_by_impl(map([f]));",
|
||||
"def min_by(f): _min_by_impl(map([f]));",
|
||||
};
|
||||
|
||||
|
||||
block builtins_bind(block b) {
|
||||
for (int i=(int)(sizeof(jq_builtins)/sizeof(jq_builtins[0]))-1; i>=0; i--) {
|
||||
struct locfile src;
|
||||
locfile_init(&src, jq_builtins[i], strlen(jq_builtins[i]));
|
||||
block funcs;
|
||||
int nerrors = jq_parse_library(&src, &funcs);
|
||||
assert(!nerrors);
|
||||
b = block_bind(funcs, b, OP_IS_CALL_PSEUDO);
|
||||
locfile_free(&src);
|
||||
}
|
||||
b = bind_bytecoded_builtins(b);
|
||||
return gen_cbinding(&cbuiltins, b);
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
#ifndef BUILTIN_H
|
||||
#define BUILTIN_H
|
||||
|
||||
#include "compile.h"
|
||||
|
||||
block builtins_bind(block);
|
||||
|
||||
|
||||
typedef void (*cfunction_ptr)(void);
|
||||
|
||||
struct cfunction {
|
||||
cfunction_ptr fptr;
|
||||
const char* name;
|
||||
int nargs;
|
||||
};
|
||||
|
||||
|
||||
jv cfunction_invoke(struct cfunction* function, jv input[]);
|
||||
|
||||
|
||||
#endif
|
||||
+89
@@ -0,0 +1,89 @@
|
||||
#include <stdio.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "bytecode.h"
|
||||
#include "opcode.h"
|
||||
#include "jv_alloc.h"
|
||||
|
||||
static int bytecode_operation_length(uint16_t* codeptr) {
|
||||
int length = opcode_describe(*codeptr)->length;
|
||||
if (*codeptr == CALL_JQ) {
|
||||
length += codeptr[1] * 2;
|
||||
}
|
||||
return length;
|
||||
}
|
||||
|
||||
void dump_disassembly(int indent, struct bytecode* bc) {
|
||||
if (bc->nclosures > 0) {
|
||||
printf("%*s[params: %d]\n", indent, "", bc->nclosures);
|
||||
}
|
||||
dump_code(indent, bc);
|
||||
for (int i=0; i<bc->nsubfunctions; i++) {
|
||||
printf("%*ssubfn[%d]:\n", indent, "", i);
|
||||
dump_disassembly(indent+2, bc->subfunctions[i]);
|
||||
}
|
||||
}
|
||||
|
||||
void dump_code(int indent, struct bytecode* bc) {
|
||||
int pc = 0;
|
||||
while (pc < bc->codelen) {
|
||||
printf("%*s", indent, "");
|
||||
dump_operation(bc, bc->code + pc);
|
||||
printf("\n");
|
||||
pc += bytecode_operation_length(bc->code + pc);
|
||||
}
|
||||
}
|
||||
|
||||
void dump_operation(struct bytecode* bc, uint16_t* codeptr) {
|
||||
int pc = codeptr - bc->code;
|
||||
printf("%04d ", pc);
|
||||
const struct opcode_description* op = opcode_describe(bc->code[pc++]);
|
||||
printf("%s", op->name);
|
||||
if (op->length > 1) {
|
||||
uint16_t imm = bc->code[pc++];
|
||||
if (op->op == CALL_JQ) {
|
||||
for (int i=0; i<imm+1; i++) {
|
||||
uint16_t level = bc->code[pc++];
|
||||
uint16_t idx = bc->code[pc++];
|
||||
if (idx & ARG_NEWCLOSURE) {
|
||||
printf(" subfn[%d]", idx & ~ARG_NEWCLOSURE);
|
||||
} else {
|
||||
printf(" param[%d]", idx);
|
||||
}
|
||||
if (level) {
|
||||
printf("^%d", level);
|
||||
}
|
||||
}
|
||||
} else if (op->flags & OP_HAS_BRANCH) {
|
||||
printf(" %04d", pc + imm);
|
||||
} else if (op->flags & OP_HAS_CONSTANT) {
|
||||
printf(" ");
|
||||
jv_dump(jv_array_get(jv_copy(bc->constants), imm), 0);
|
||||
} else if (op->flags & OP_HAS_VARIABLE) {
|
||||
uint16_t v = bc->code[pc++];
|
||||
printf(" v%d", v);
|
||||
if (imm) {
|
||||
printf("^%d", imm);
|
||||
}
|
||||
} else {
|
||||
printf(" %d", imm);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void symbol_table_free(struct symbol_table* syms) {
|
||||
jv_mem_free(syms->cfunctions);
|
||||
jv_mem_free(syms);
|
||||
}
|
||||
|
||||
void bytecode_free(struct bytecode* bc) {
|
||||
jv_mem_free(bc->code);
|
||||
jv_free(bc->constants);
|
||||
for (int i=0; i<bc->nsubfunctions; i++)
|
||||
bytecode_free(bc->subfunctions[i]);
|
||||
if (!bc->parent)
|
||||
symbol_table_free(bc->globals);
|
||||
jv_mem_free(bc->subfunctions);
|
||||
jv_mem_free(bc);
|
||||
}
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
#ifndef BYTECODE_H
|
||||
#define BYTECODE_H
|
||||
#include <stdint.h>
|
||||
|
||||
#include "jv.h"
|
||||
#include "opcode.h"
|
||||
#include "builtin.h"
|
||||
|
||||
#define MAX_CFUNCTION_ARGS 10
|
||||
struct symbol_table {
|
||||
struct cfunction* cfunctions;
|
||||
int ncfunctions;
|
||||
};
|
||||
|
||||
// The bytecode format matters in:
|
||||
// execute.c - interpreter
|
||||
// compile.c - compiler
|
||||
// bytecode.c - disassembler
|
||||
|
||||
#define ARG_NEWCLOSURE 0x1000
|
||||
|
||||
struct bytecode {
|
||||
uint16_t* code;
|
||||
int codelen;
|
||||
|
||||
int nlocals;
|
||||
int nclosures;
|
||||
|
||||
jv constants; // JSON array of constants
|
||||
struct symbol_table* globals;
|
||||
|
||||
struct bytecode** subfunctions;
|
||||
int nsubfunctions;
|
||||
|
||||
struct bytecode* parent;
|
||||
};
|
||||
|
||||
void dump_disassembly(int, struct bytecode* code);
|
||||
void dump_code(int, struct bytecode* code);
|
||||
void dump_operation(struct bytecode* bc, uint16_t* op);
|
||||
|
||||
void symbol_table_free(struct symbol_table* syms);
|
||||
void bytecode_free(struct bytecode* bc);
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,649 @@
|
||||
#define _GNU_SOURCE // for strdup
|
||||
#include <assert.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include "opcode.h"
|
||||
#include "compile.h"
|
||||
#include "bytecode.h"
|
||||
#include "locfile.h"
|
||||
#include "jv_alloc.h"
|
||||
|
||||
/*
|
||||
The intermediate representation for jq filters is as a sequence of
|
||||
struct inst, which form a doubly-linked list via the next and prev
|
||||
pointers.
|
||||
|
||||
A "block" represents a sequence of "struct inst", which may be
|
||||
empty.
|
||||
|
||||
Blocks are generated by the parser bottom-up, so may have free
|
||||
variables (refer to things not defined). See inst.bound_by and
|
||||
inst.symbol.
|
||||
*/
|
||||
struct inst {
|
||||
struct inst* next;
|
||||
struct inst* prev;
|
||||
|
||||
opcode op;
|
||||
|
||||
struct {
|
||||
uint16_t intval;
|
||||
struct inst* target;
|
||||
jv constant;
|
||||
struct cfunction* cfunc;
|
||||
} imm;
|
||||
|
||||
location source;
|
||||
|
||||
// Binding
|
||||
// An instruction requiring binding (for parameters/variables/functions)
|
||||
// is in one of three states:
|
||||
// inst->bound_by = NULL - Unbound free variable
|
||||
// inst->bound_by = inst - This instruction binds a variable
|
||||
// inst->bound_by = other - Uses variable bound by other instruction
|
||||
// Unbound instructions (references to other things that may or may not
|
||||
// exist) are created by "gen_foo_unbound", and bindings are created by
|
||||
// block_bind(definition, body), which binds all instructions in
|
||||
// body which are unboudn and refer to "definition" by name.
|
||||
struct inst* bound_by;
|
||||
char* symbol;
|
||||
|
||||
block subfn; // used by CLOSURE_CREATE (body of function)
|
||||
block arglist; // used by CLOSURE_CREATE (formals) and CALL_JQ (arguments)
|
||||
|
||||
// This instruction is compiled as part of which function?
|
||||
// (only used during block_compile)
|
||||
struct bytecode* compiled;
|
||||
|
||||
int bytecode_pos; // position just after this insn
|
||||
};
|
||||
|
||||
static inst* inst_new(opcode op) {
|
||||
inst* i = jv_mem_alloc(sizeof(inst));
|
||||
i->next = i->prev = 0;
|
||||
i->op = op;
|
||||
i->bytecode_pos = -1;
|
||||
i->bound_by = 0;
|
||||
i->symbol = 0;
|
||||
i->subfn = gen_noop();
|
||||
i->arglist = gen_noop();
|
||||
i->source = UNKNOWN_LOCATION;
|
||||
return i;
|
||||
}
|
||||
|
||||
static void inst_free(struct inst* i) {
|
||||
jv_mem_free(i->symbol);
|
||||
block_free(i->subfn);
|
||||
block_free(i->arglist);
|
||||
if (opcode_describe(i->op)->flags & OP_HAS_CONSTANT) {
|
||||
jv_free(i->imm.constant);
|
||||
}
|
||||
jv_mem_free(i);
|
||||
}
|
||||
|
||||
static block inst_block(inst* i) {
|
||||
block b = {i,i};
|
||||
return b;
|
||||
}
|
||||
|
||||
static int block_is_single(block b) {
|
||||
return b.first && b.first == b.last;
|
||||
}
|
||||
|
||||
static inst* block_take(block* b) {
|
||||
if (b->first == 0) return 0;
|
||||
inst* i = b->first;
|
||||
if (i->next) {
|
||||
i->next->prev = 0;
|
||||
b->first = i->next;
|
||||
i->next = 0;
|
||||
} else {
|
||||
b->first = 0;
|
||||
b->last = 0;
|
||||
}
|
||||
return i;
|
||||
}
|
||||
|
||||
block gen_location(location loc, block b) {
|
||||
for (inst* i = b.first; i; i = i->next) {
|
||||
if (i->source.start == UNKNOWN_LOCATION.start &&
|
||||
i->source.end == UNKNOWN_LOCATION.end) {
|
||||
i->source = loc;
|
||||
}
|
||||
}
|
||||
return b;
|
||||
}
|
||||
|
||||
block gen_noop() {
|
||||
block b = {0,0};
|
||||
return b;
|
||||
}
|
||||
|
||||
block gen_op_simple(opcode op) {
|
||||
assert(opcode_describe(op)->length == 1);
|
||||
return inst_block(inst_new(op));
|
||||
}
|
||||
|
||||
|
||||
block gen_const(jv constant) {
|
||||
assert(opcode_describe(LOADK)->flags & OP_HAS_CONSTANT);
|
||||
inst* i = inst_new(LOADK);
|
||||
i->imm.constant = constant;
|
||||
return inst_block(i);
|
||||
}
|
||||
|
||||
block gen_op_target(opcode op, block target) {
|
||||
assert(opcode_describe(op)->flags & OP_HAS_BRANCH);
|
||||
assert(target.last);
|
||||
inst* i = inst_new(op);
|
||||
i->imm.target = target.last;
|
||||
return inst_block(i);
|
||||
}
|
||||
|
||||
block gen_op_targetlater(opcode op) {
|
||||
assert(opcode_describe(op)->flags & OP_HAS_BRANCH);
|
||||
inst* i = inst_new(op);
|
||||
i->imm.target = 0;
|
||||
return inst_block(i);
|
||||
}
|
||||
void inst_set_target(block b, block target) {
|
||||
assert(block_is_single(b));
|
||||
assert(opcode_describe(b.first->op)->flags & OP_HAS_BRANCH);
|
||||
assert(target.last);
|
||||
b.first->imm.target = target.last;
|
||||
}
|
||||
|
||||
block gen_op_var_unbound(opcode op, const char* name) {
|
||||
assert(opcode_describe(op)->flags & OP_HAS_VARIABLE);
|
||||
inst* i = inst_new(op);
|
||||
i->symbol = strdup(name);
|
||||
return inst_block(i);
|
||||
}
|
||||
|
||||
block gen_op_var_bound(opcode op, block binder) {
|
||||
assert(block_is_single(binder));
|
||||
block b = gen_op_var_unbound(op, binder.first->symbol);
|
||||
b.first->bound_by = binder.first;
|
||||
return b;
|
||||
}
|
||||
|
||||
block gen_op_block_unbound(opcode op, const char* name) {
|
||||
assert(opcode_describe(op)->flags & OP_IS_CALL_PSEUDO);
|
||||
inst* i = inst_new(op);
|
||||
i->symbol = strdup(name);
|
||||
return inst_block(i);
|
||||
}
|
||||
|
||||
block gen_op_block_bound(opcode op, block binder) {
|
||||
assert(block_is_single(binder));
|
||||
block b = gen_op_block_unbound(op, binder.first->symbol);
|
||||
b.first->bound_by = binder.first;
|
||||
return b;
|
||||
}
|
||||
|
||||
static void inst_join(inst* a, inst* b) {
|
||||
assert(a && b);
|
||||
assert(!a->next);
|
||||
assert(!b->prev);
|
||||
a->next = b;
|
||||
b->prev = a;
|
||||
}
|
||||
|
||||
void block_append(block* b, block b2) {
|
||||
if (b2.first) {
|
||||
if (b->last) {
|
||||
inst_join(b->last, b2.first);
|
||||
} else {
|
||||
b->first = b2.first;
|
||||
}
|
||||
b->last = b2.last;
|
||||
}
|
||||
}
|
||||
|
||||
block block_join(block a, block b) {
|
||||
block c = a;
|
||||
block_append(&c, b);
|
||||
return c;
|
||||
}
|
||||
|
||||
int block_has_only_binders(block binders, int bindflags) {
|
||||
bindflags |= OP_HAS_BINDING;
|
||||
for (inst* curr = binders.first; curr; curr = curr->next) {
|
||||
if ((opcode_describe(curr->op)->flags & bindflags) != bindflags) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
static void block_bind_subblock(block binder, block body, int bindflags) {
|
||||
assert(block_is_single(binder));
|
||||
assert((opcode_describe(binder.first->op)->flags & bindflags) == bindflags);
|
||||
assert(binder.first->symbol);
|
||||
assert(binder.first->bound_by == 0 || binder.first->bound_by == binder.first);
|
||||
|
||||
binder.first->bound_by = binder.first;
|
||||
for (inst* i = body.first; i; i = i->next) {
|
||||
int flags = opcode_describe(i->op)->flags;
|
||||
if ((flags & bindflags) == bindflags &&
|
||||
i->bound_by == 0 &&
|
||||
!strcmp(i->symbol, binder.first->symbol)) {
|
||||
// bind this instruction
|
||||
i->bound_by = binder.first;
|
||||
}
|
||||
// binding recurses into closures
|
||||
block_bind_subblock(binder, i->subfn, bindflags);
|
||||
// binding recurses into argument list
|
||||
block_bind_subblock(binder, i->arglist, bindflags);
|
||||
}
|
||||
}
|
||||
|
||||
static void block_bind_each(block binder, block body, int bindflags) {
|
||||
assert(block_has_only_binders(binder, bindflags));
|
||||
bindflags |= OP_HAS_BINDING;
|
||||
for (inst* curr = binder.first; curr; curr = curr->next) {
|
||||
block_bind_subblock(inst_block(curr), body, bindflags);
|
||||
}
|
||||
}
|
||||
|
||||
block block_bind(block binder, block body, int bindflags) {
|
||||
block_bind_each(binder, body, bindflags);
|
||||
return block_join(binder, body);
|
||||
}
|
||||
|
||||
block gen_function(const char* name, block formals, block body) {
|
||||
block_bind_each(formals, body, OP_IS_CALL_PSEUDO);
|
||||
inst* i = inst_new(CLOSURE_CREATE);
|
||||
i->subfn = body;
|
||||
i->symbol = strdup(name);
|
||||
i->arglist = formals;
|
||||
block b = inst_block(i);
|
||||
block_bind_subblock(b, b, OP_IS_CALL_PSEUDO | OP_HAS_BINDING);
|
||||
return b;
|
||||
}
|
||||
|
||||
block gen_lambda(block body) {
|
||||
return gen_function("@lambda", gen_noop(), body);
|
||||
}
|
||||
|
||||
block gen_call(const char* name, block args) {
|
||||
block b = gen_op_block_unbound(CALL_JQ, name);
|
||||
b.first->arglist = args;
|
||||
return b;
|
||||
}
|
||||
|
||||
|
||||
|
||||
block gen_subexp(block a) {
|
||||
return BLOCK(gen_op_simple(DUP), a, gen_op_simple(SWAP));
|
||||
}
|
||||
|
||||
block gen_both(block a, block b) {
|
||||
block jump = gen_op_targetlater(JUMP);
|
||||
block fork = gen_op_target(FORK, jump);
|
||||
block c = BLOCK(fork, a, jump, b);
|
||||
inst_set_target(jump, c);
|
||||
return c;
|
||||
}
|
||||
|
||||
|
||||
block gen_collect(block expr) {
|
||||
block array_var = block_bind(gen_op_var_unbound(STOREV, "collect"),
|
||||
gen_noop(), OP_HAS_VARIABLE);
|
||||
block c = BLOCK(gen_op_simple(DUP), gen_const(jv_array()), array_var);
|
||||
|
||||
block tail = BLOCK(gen_op_simple(DUP),
|
||||
gen_op_var_bound(LOADV, array_var),
|
||||
gen_op_simple(SWAP),
|
||||
gen_op_simple(APPEND),
|
||||
gen_op_var_bound(STOREV, array_var),
|
||||
gen_op_simple(BACKTRACK));
|
||||
|
||||
return BLOCK(c,
|
||||
gen_op_target(FORK, tail),
|
||||
expr,
|
||||
tail,
|
||||
gen_op_var_bound(LOADV, array_var));
|
||||
}
|
||||
|
||||
block gen_assign(block expr) {
|
||||
block result_var = block_bind(gen_op_var_unbound(STOREV, "result"),
|
||||
gen_noop(), OP_HAS_VARIABLE);
|
||||
|
||||
block loop = BLOCK(gen_op_simple(DUP),
|
||||
expr,
|
||||
gen_op_var_bound(ASSIGN, result_var),
|
||||
gen_op_simple(BACKTRACK));
|
||||
|
||||
return BLOCK(gen_op_simple(DUP),
|
||||
result_var,
|
||||
gen_op_target(FORK, loop),
|
||||
loop,
|
||||
gen_op_var_bound(LOADV, result_var));
|
||||
}
|
||||
|
||||
block gen_definedor(block a, block b) {
|
||||
// var found := false
|
||||
block found_var = block_bind(gen_op_var_unbound(STOREV, "found"),
|
||||
gen_noop(), OP_HAS_VARIABLE);
|
||||
block init = BLOCK(gen_op_simple(DUP), gen_const(jv_false()), found_var);
|
||||
|
||||
// if found, backtrack. Otherwise execute b
|
||||
block backtrack = gen_op_simple(BACKTRACK);
|
||||
block tail = BLOCK(gen_op_simple(DUP),
|
||||
gen_op_var_bound(LOADV, found_var),
|
||||
gen_op_target(JUMP_F, backtrack),
|
||||
backtrack,
|
||||
gen_op_simple(POP),
|
||||
b);
|
||||
|
||||
// try again
|
||||
block if_notfound = gen_op_simple(BACKTRACK);
|
||||
|
||||
// found := true, produce result
|
||||
block if_found = BLOCK(gen_op_simple(DUP),
|
||||
gen_const(jv_true()),
|
||||
gen_op_var_bound(STOREV, found_var),
|
||||
gen_op_target(JUMP, tail));
|
||||
|
||||
return BLOCK(init,
|
||||
gen_op_target(FORK, if_notfound),
|
||||
a,
|
||||
gen_op_target(JUMP_F, if_found),
|
||||
if_found,
|
||||
if_notfound,
|
||||
tail);
|
||||
}
|
||||
|
||||
block gen_condbranch(block iftrue, block iffalse) {
|
||||
iftrue = BLOCK(iftrue, gen_op_target(JUMP, iffalse));
|
||||
return BLOCK(gen_op_target(JUMP_F, iftrue), iftrue, iffalse);
|
||||
}
|
||||
|
||||
block gen_and(block a, block b) {
|
||||
// a and b = if a then (if b then true else false) else false
|
||||
return BLOCK(gen_op_simple(DUP), a,
|
||||
gen_condbranch(BLOCK(gen_op_simple(POP),
|
||||
b,
|
||||
gen_condbranch(gen_const(jv_true()),
|
||||
gen_const(jv_false()))),
|
||||
BLOCK(gen_op_simple(POP), gen_const(jv_false()))));
|
||||
}
|
||||
|
||||
block gen_or(block a, block b) {
|
||||
// a or b = if a then true else (if b then true else false)
|
||||
return BLOCK(gen_op_simple(DUP), a,
|
||||
gen_condbranch(BLOCK(gen_op_simple(POP), gen_const(jv_true())),
|
||||
BLOCK(gen_op_simple(POP),
|
||||
b,
|
||||
gen_condbranch(gen_const(jv_true()),
|
||||
gen_const(jv_false())))));
|
||||
}
|
||||
|
||||
block gen_cond(block cond, block iftrue, block iffalse) {
|
||||
return BLOCK(gen_op_simple(DUP), cond,
|
||||
gen_condbranch(BLOCK(gen_op_simple(POP), iftrue),
|
||||
BLOCK(gen_op_simple(POP), iffalse)));
|
||||
}
|
||||
|
||||
block gen_cbinding(struct symbol_table* t, block code) {
|
||||
for (int cfunc=0; cfunc<t->ncfunctions; cfunc++) {
|
||||
inst* i = inst_new(CLOSURE_CREATE_C);
|
||||
i->imm.cfunc = &t->cfunctions[cfunc];
|
||||
i->symbol = strdup(i->imm.cfunc->name);
|
||||
code = block_bind(inst_block(i), code, OP_IS_CALL_PSEUDO);
|
||||
}
|
||||
return code;
|
||||
}
|
||||
|
||||
static uint16_t nesting_level(struct bytecode* bc, inst* target) {
|
||||
uint16_t level = 0;
|
||||
assert(bc && target->compiled);
|
||||
while (bc && target->compiled != bc) {
|
||||
level++;
|
||||
bc = bc->parent;
|
||||
}
|
||||
assert(bc && bc == target->compiled);
|
||||
return level;
|
||||
}
|
||||
|
||||
static int count_cfunctions(block b) {
|
||||
int n = 0;
|
||||
for (inst* i = b.first; i; i = i->next) {
|
||||
if (i->op == CLOSURE_CREATE_C) n++;
|
||||
n += count_cfunctions(i->subfn);
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
|
||||
// Expands call instructions into a calling sequence
|
||||
static int expand_call_arglist(struct locfile* locations, block* b) {
|
||||
int errors = 0;
|
||||
block ret = gen_noop();
|
||||
for (inst* curr; (curr = block_take(b));) {
|
||||
if (opcode_describe(curr->op)->flags & OP_HAS_BINDING) {
|
||||
if (!curr->bound_by) {
|
||||
locfile_locate(locations, curr->source, "error: %s is not defined", curr->symbol);
|
||||
errors++;
|
||||
// don't process this instruction if it's not well-defined
|
||||
ret = BLOCK(ret, inst_block(curr));
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
block prelude = gen_noop();
|
||||
if (curr->op == CALL_JQ) {
|
||||
int actual_args = 0, desired_args = 0;
|
||||
// We expand the argument list as a series of instructions
|
||||
switch (curr->bound_by->op) {
|
||||
default: assert(0 && "Unknown function type"); break;
|
||||
case CLOSURE_CREATE:
|
||||
case CLOSURE_PARAM: {
|
||||
block callargs = gen_noop();
|
||||
for (inst* i; (i = block_take(&curr->arglist));) {
|
||||
assert(opcode_describe(i->op)->flags & OP_IS_CALL_PSEUDO);
|
||||
block b = inst_block(i);
|
||||
switch (i->op) {
|
||||
default: assert(0 && "Unknown type of parameter"); break;
|
||||
case CLOSURE_REF:
|
||||
block_append(&callargs, b);
|
||||
break;
|
||||
case CLOSURE_CREATE:
|
||||
block_append(&prelude, b);
|
||||
block_append(&callargs, gen_op_block_bound(CLOSURE_REF, b));
|
||||
break;
|
||||
}
|
||||
actual_args++;
|
||||
}
|
||||
curr->imm.intval = actual_args;
|
||||
curr->arglist = callargs;
|
||||
|
||||
if (curr->bound_by->op == CLOSURE_CREATE) {
|
||||
for (inst* i = curr->bound_by->arglist.first; i; i = i->next) {
|
||||
assert(i->op == CLOSURE_PARAM);
|
||||
desired_args++;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case CLOSURE_CREATE_C: {
|
||||
for (inst* i; (i = block_take(&curr->arglist)); ) {
|
||||
assert(i->op == CLOSURE_CREATE); // FIXME
|
||||
block body = i->subfn;
|
||||
i->subfn = gen_noop();
|
||||
inst_free(i);
|
||||
// arguments should be pushed in reverse order, prepend them to prelude
|
||||
errors += expand_call_arglist(locations, &body);
|
||||
prelude = BLOCK(gen_subexp(body), prelude);
|
||||
actual_args++;
|
||||
}
|
||||
assert(curr->op == CALL_JQ);
|
||||
curr->op = CALL_BUILTIN;
|
||||
curr->imm.intval = actual_args + 1 /* include the implicit input in arg count */;
|
||||
assert(curr->bound_by->op == CLOSURE_CREATE_C);
|
||||
desired_args = curr->bound_by->imm.cfunc->nargs - 1;
|
||||
assert(!curr->arglist.first);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (actual_args != desired_args) {
|
||||
locfile_locate(locations, curr->source,
|
||||
"error: %s arguments to %s (expected %d but got %d)",
|
||||
actual_args > desired_args ? "too many" : "too few",
|
||||
curr->symbol, desired_args, actual_args);
|
||||
errors++;
|
||||
}
|
||||
|
||||
}
|
||||
ret = BLOCK(ret, prelude, inst_block(curr));
|
||||
}
|
||||
*b = ret;
|
||||
return errors;
|
||||
}
|
||||
|
||||
static int compile(struct locfile* locations, struct bytecode* bc, block b) {
|
||||
int errors = 0;
|
||||
int pos = 0;
|
||||
int var_frame_idx = 0;
|
||||
bc->nsubfunctions = 0;
|
||||
errors += expand_call_arglist(locations, &b);
|
||||
if (bc->parent) {
|
||||
// functions should end in a return
|
||||
b = BLOCK(b, gen_op_simple(RET));
|
||||
} else {
|
||||
// the toplevel should YIELD;BACKTRACK; when it finds an answer
|
||||
b = BLOCK(b, gen_op_simple(YIELD), gen_op_simple(BACKTRACK));
|
||||
}
|
||||
for (inst* curr = b.first; curr; curr = curr->next) {
|
||||
if (!curr->next) assert(curr == b.last);
|
||||
int length = opcode_describe(curr->op)->length;
|
||||
if (curr->op == CALL_JQ) {
|
||||
for (inst* arg = curr->arglist.first; arg; arg = arg->next) {
|
||||
length += 2;
|
||||
}
|
||||
}
|
||||
pos += length;
|
||||
curr->bytecode_pos = pos;
|
||||
curr->compiled = bc;
|
||||
|
||||
assert(curr->op != CLOSURE_REF && curr->op != CLOSURE_PARAM);
|
||||
|
||||
if ((opcode_describe(curr->op)->flags & OP_HAS_VARIABLE) &&
|
||||
curr->bound_by == curr) {
|
||||
curr->imm.intval = var_frame_idx++;
|
||||
}
|
||||
|
||||
if (curr->op == CLOSURE_CREATE) {
|
||||
assert(curr->bound_by == curr);
|
||||
curr->imm.intval = bc->nsubfunctions++;
|
||||
}
|
||||
if (curr->op == CLOSURE_CREATE_C) {
|
||||
assert(curr->bound_by == curr);
|
||||
int idx = bc->globals->ncfunctions++;
|
||||
bc->globals->cfunctions[idx] = *curr->imm.cfunc;
|
||||
curr->imm.intval = idx;
|
||||
}
|
||||
}
|
||||
if (bc->nsubfunctions) {
|
||||
bc->subfunctions = jv_mem_alloc(sizeof(struct bytecode*) * bc->nsubfunctions);
|
||||
for (inst* curr = b.first; curr; curr = curr->next) {
|
||||
if (curr->op == CLOSURE_CREATE) {
|
||||
struct bytecode* subfn = jv_mem_alloc(sizeof(struct bytecode));
|
||||
bc->subfunctions[curr->imm.intval] = subfn;
|
||||
subfn->globals = bc->globals;
|
||||
subfn->parent = bc;
|
||||
subfn->nclosures = 0;
|
||||
for (inst* param = curr->arglist.first; param; param = param->next) {
|
||||
assert(param->op == CLOSURE_PARAM);
|
||||
assert(param->bound_by == param);
|
||||
param->imm.intval = subfn->nclosures++;
|
||||
param->compiled = subfn;
|
||||
}
|
||||
errors += compile(locations, subfn, curr->subfn);
|
||||
curr->subfn = gen_noop();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
bc->subfunctions = 0;
|
||||
}
|
||||
bc->codelen = pos;
|
||||
uint16_t* code = jv_mem_alloc(sizeof(uint16_t) * bc->codelen);
|
||||
bc->code = code;
|
||||
pos = 0;
|
||||
jv constant_pool = jv_array();
|
||||
int maxvar = -1;
|
||||
if (!errors) for (inst* curr = b.first; curr; curr = curr->next) {
|
||||
const struct opcode_description* op = opcode_describe(curr->op);
|
||||
if (op->length == 0)
|
||||
continue;
|
||||
code[pos++] = curr->op;
|
||||
assert(curr->op != CLOSURE_REF && curr->op != CLOSURE_PARAM);
|
||||
if (curr->op == CALL_BUILTIN) {
|
||||
assert(curr->bound_by->op == CLOSURE_CREATE_C);
|
||||
assert(!curr->arglist.first);
|
||||
code[pos++] = (uint16_t)curr->imm.intval;
|
||||
code[pos++] = curr->bound_by->imm.intval;
|
||||
} else if (curr->op == CALL_JQ) {
|
||||
assert(curr->bound_by->op == CLOSURE_CREATE ||
|
||||
curr->bound_by->op == CLOSURE_PARAM);
|
||||
code[pos++] = (uint16_t)curr->imm.intval;
|
||||
code[pos++] = nesting_level(bc, curr->bound_by);
|
||||
code[pos++] = curr->bound_by->imm.intval |
|
||||
(curr->bound_by->op == CLOSURE_CREATE ? ARG_NEWCLOSURE : 0);
|
||||
for (inst* arg = curr->arglist.first; arg; arg = arg->next) {
|
||||
assert(arg->op == CLOSURE_REF && arg->bound_by->op == CLOSURE_CREATE);
|
||||
code[pos++] = nesting_level(bc, arg->bound_by);
|
||||
code[pos++] = arg->bound_by->imm.intval | ARG_NEWCLOSURE;
|
||||
}
|
||||
} else if (op->flags & OP_HAS_CONSTANT) {
|
||||
code[pos++] = jv_array_length(jv_copy(constant_pool));
|
||||
constant_pool = jv_array_append(constant_pool, jv_copy(curr->imm.constant));
|
||||
} else if (op->flags & OP_HAS_VARIABLE) {
|
||||
code[pos++] = nesting_level(bc, curr->bound_by);
|
||||
uint16_t var = (uint16_t)curr->bound_by->imm.intval;
|
||||
code[pos++] = var;
|
||||
if (var > maxvar) maxvar = var;
|
||||
} else if (op->flags & OP_HAS_BRANCH) {
|
||||
assert(curr->imm.target->bytecode_pos != -1);
|
||||
assert(curr->imm.target->bytecode_pos > pos); // only forward branches
|
||||
code[pos] = curr->imm.target->bytecode_pos - (pos + 1);
|
||||
pos++;
|
||||
} else if (op->length > 1) {
|
||||
assert(0 && "codegen not implemented for this operation");
|
||||
}
|
||||
}
|
||||
bc->constants = constant_pool;
|
||||
bc->nlocals = maxvar + 2; // FIXME: frames of size zero?
|
||||
block_free(b);
|
||||
return errors;
|
||||
}
|
||||
|
||||
int block_compile(block b, struct locfile* locations, struct bytecode** out) {
|
||||
struct bytecode* bc = jv_mem_alloc(sizeof(struct bytecode));
|
||||
bc->parent = 0;
|
||||
bc->nclosures = 0;
|
||||
bc->globals = jv_mem_alloc(sizeof(struct symbol_table));
|
||||
int ncfunc = count_cfunctions(b);
|
||||
bc->globals->ncfunctions = 0;
|
||||
bc->globals->cfunctions = jv_mem_alloc(sizeof(struct cfunction) * ncfunc);
|
||||
int nerrors = compile(locations, bc, b);
|
||||
assert(bc->globals->ncfunctions == ncfunc);
|
||||
if (nerrors > 0) {
|
||||
bytecode_free(bc);
|
||||
*out = 0;
|
||||
} else {
|
||||
*out = bc;
|
||||
}
|
||||
return nerrors;
|
||||
}
|
||||
|
||||
void block_free(block b) {
|
||||
struct inst* next;
|
||||
for (struct inst* curr = b.first; curr; curr = next) {
|
||||
next = curr->next;
|
||||
inst_free(curr);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
#ifndef COMPILE_H
|
||||
#define COMPILE_H
|
||||
#include <stdint.h>
|
||||
#include "jv.h"
|
||||
#include "opcode.h"
|
||||
#include "locfile.h"
|
||||
|
||||
struct bytecode;
|
||||
struct symbol_table;
|
||||
|
||||
struct inst;
|
||||
typedef struct inst inst;
|
||||
|
||||
|
||||
typedef struct block {
|
||||
inst* first;
|
||||
inst* last;
|
||||
} block;
|
||||
|
||||
block gen_location(location, block);
|
||||
|
||||
block gen_noop();
|
||||
block gen_op_simple(opcode op);
|
||||
block gen_const(jv constant);
|
||||
block gen_op_target(opcode op, block target);
|
||||
block gen_op_var_unbound(opcode op, const char* name);
|
||||
block gen_op_var_bound(opcode op, block binder);
|
||||
block gen_op_block_unbound(opcode op, const char* name);
|
||||
|
||||
block gen_function(const char* name, block formals, block body);
|
||||
block gen_lambda(block body);
|
||||
block gen_call(const char* name, block body);
|
||||
block gen_subexp(block a);
|
||||
block gen_both(block a, block b);
|
||||
block gen_collect(block expr);
|
||||
block gen_assign(block expr);
|
||||
block gen_definedor(block a, block b);
|
||||
block gen_condbranch(block iftrue, block iffalse);
|
||||
block gen_and(block a, block b);
|
||||
block gen_or(block a, block b);
|
||||
|
||||
block gen_cond(block cond, block iftrue, block iffalse);
|
||||
|
||||
block gen_cbinding(struct symbol_table* functions, block b);
|
||||
|
||||
void block_append(block* b, block b2);
|
||||
block block_join(block a, block b);
|
||||
int block_has_only_binders(block, int bindflags);
|
||||
block block_bind(block binder, block body, int bindflags);
|
||||
|
||||
int block_compile(block, struct locfile*, struct bytecode**);
|
||||
|
||||
void block_free(block);
|
||||
|
||||
|
||||
|
||||
// Here's some horrible preprocessor gunk so that code
|
||||
// sequences can be contructed as BLOCK(block1, block2, block3)
|
||||
|
||||
#define BLOCK_1(b1) (b1)
|
||||
#define BLOCK_2(b1,b2) (block_join((b1),(b2)))
|
||||
#define BLOCK_3(b1,b2,b3) (block_join(BLOCK_2(b1,b2),(b3)))
|
||||
#define BLOCK_4(b1,b2,b3,b4) (block_join(BLOCK_3(b1,b2,b3),(b4)))
|
||||
#define BLOCK_5(b1,b2,b3,b4,b5) (block_join(BLOCK_4(b1,b2,b3,b4),(b5)))
|
||||
#define BLOCK_6(b1,b2,b3,b4,b5,b6) (block_join(BLOCK_5(b1,b2,b3,b4,b5),(b6)))
|
||||
#define BLOCK_7(b1,b2,b3,b4,b5,b6,b7) (block_join(BLOCK_6(b1,b2,b3,b4,b5,b6),(b7)))
|
||||
|
||||
#define BLOCK_IDX(_1,_2,_3,_4,_5,_6,_7,NAME,...) NAME
|
||||
#define BLOCK(...) \
|
||||
BLOCK_IDX(__VA_ARGS__, BLOCK_7, BLOCK_6, BLOCK_5, BLOCK_4, BLOCK_3, BLOCK_2, BLOCK_1)(__VA_ARGS__)
|
||||
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,6 @@
|
||||
.DS_Store
|
||||
.sass-cache
|
||||
output/*
|
||||
|
||||
# Autogenerated from public/css/base.scss
|
||||
public/css/base.css
|
||||
@@ -0,0 +1,5 @@
|
||||
source :rubygems
|
||||
|
||||
gem "bonsai"
|
||||
gem "maruku"
|
||||
gem "ronn"
|
||||
@@ -0,0 +1,53 @@
|
||||
GEM
|
||||
remote: http://rubygems.org/
|
||||
specs:
|
||||
activesupport (3.2.9)
|
||||
i18n (~> 0.6)
|
||||
multi_json (~> 1.0)
|
||||
addressable (2.3.2)
|
||||
bonsai (1.4.7)
|
||||
activesupport (>= 3.0.3)
|
||||
builder (>= 3.0.0)
|
||||
i18n (>= 0.5.0)
|
||||
launchy (>= 0.3.7)
|
||||
liquid (>= 2.2.2)
|
||||
maruku (>= 0.6.0)
|
||||
rack
|
||||
sass
|
||||
sinatra (>= 1.0)
|
||||
tilt (>= 1.3)
|
||||
watch (>= 0.1.0)
|
||||
builder (3.1.4)
|
||||
hpricot (0.8.6)
|
||||
i18n (0.6.1)
|
||||
launchy (2.1.2)
|
||||
addressable (~> 2.3)
|
||||
liquid (2.4.1)
|
||||
maruku (0.6.1)
|
||||
syntax (>= 1.0.0)
|
||||
multi_json (1.5.0)
|
||||
mustache (0.99.4)
|
||||
rack (1.4.1)
|
||||
rack-protection (1.3.2)
|
||||
rack
|
||||
rdiscount (1.6.8)
|
||||
ronn (0.7.3)
|
||||
hpricot (>= 0.8.2)
|
||||
mustache (>= 0.7.0)
|
||||
rdiscount (>= 1.5.8)
|
||||
sass (3.2.3)
|
||||
sinatra (1.3.3)
|
||||
rack (~> 1.3, >= 1.3.6)
|
||||
rack-protection (~> 1.2)
|
||||
tilt (~> 1.3, >= 1.3.3)
|
||||
syntax (1.0.0)
|
||||
tilt (1.3.3)
|
||||
watch (0.1.0)
|
||||
|
||||
PLATFORMS
|
||||
ruby
|
||||
|
||||
DEPENDENCIES
|
||||
bonsai
|
||||
maruku
|
||||
ronn
|
||||
+110
@@ -0,0 +1,110 @@
|
||||
require 'bonsai'
|
||||
require 'liquid'
|
||||
require 'maruku'
|
||||
require 'json'
|
||||
require 'ronn'
|
||||
require 'tempfile'
|
||||
|
||||
module ExtraFilters
|
||||
def markdownify(input)
|
||||
Maruku.new(input).to_html
|
||||
end
|
||||
|
||||
def sanitize(input)
|
||||
input.gsub(/[^a-zA-Z0-9_]/,"")
|
||||
end
|
||||
|
||||
def json(input)
|
||||
input.to_json
|
||||
end
|
||||
|
||||
def unique(input)
|
||||
@n = (@n || 0) + 1
|
||||
input + @n.to_s
|
||||
end
|
||||
end
|
||||
|
||||
Liquid::Template.register_filter(ExtraFilters)
|
||||
|
||||
|
||||
task :serve do
|
||||
begin
|
||||
Bonsai.log "Press Control+C to quit"
|
||||
|
||||
require 'rack'
|
||||
require 'sinatra'
|
||||
require 'watch'
|
||||
require 'launchy'
|
||||
|
||||
Bonsai.root_dir = Dir.pwd
|
||||
|
||||
server = fork {
|
||||
app = Rack::Builder.app {
|
||||
map "/jq" do
|
||||
use Bonsai::StaticPassThrough, :root => Bonsai.root_dir + "/output", :urls => ["/"]
|
||||
end
|
||||
run Bonsai::DevelopmentServer
|
||||
}
|
||||
Rack::Handler.default.run(app, :Port => 5000) do
|
||||
Launchy.open("http://localhost:5000/jq")
|
||||
end
|
||||
}
|
||||
Watch.new("{content,templates,public}/**/*") { Bonsai::Exporter.process! }
|
||||
rescue Interrupt
|
||||
Process.kill("QUIT", server)
|
||||
Process.wait(server)
|
||||
exit
|
||||
end
|
||||
end
|
||||
|
||||
task :build do
|
||||
Bonsai.root_dir = Dir.pwd
|
||||
Bonsai::Exporter.publish!
|
||||
end
|
||||
|
||||
def load_manual
|
||||
YAML::ENGINE.yamler = 'syck'
|
||||
YAML::load(File.open("content/3.manual/manual.yml"))
|
||||
end
|
||||
|
||||
task :manpage do
|
||||
Tempfile.open "manpage" do |f|
|
||||
manual = load_manual
|
||||
f.puts manual['manpage_intro']
|
||||
f.puts manual['body']
|
||||
manual['sections'].each do |section|
|
||||
|
||||
f.puts "## #{section['title'].upcase}\n"
|
||||
f.puts section['body']
|
||||
f.puts ""
|
||||
(section['entries'] || []).each do |entry|
|
||||
f.puts "### #{entry['title']}\n"
|
||||
f.puts entry['body']
|
||||
f.puts ""
|
||||
(entry['examples'] || []).each do |example|
|
||||
f.puts " jq '#{example['program']}'"
|
||||
f.puts " #{example['input']}"
|
||||
f.puts " => #{example['output'].join(", ")}"
|
||||
f.puts
|
||||
end
|
||||
end
|
||||
f.puts ""
|
||||
end
|
||||
f.puts manual['manpage_epilogue']
|
||||
f.close
|
||||
puts Ronn::Document.new(f.path).convert('roff').gsub(/<\/?code>/,"")
|
||||
end
|
||||
end
|
||||
|
||||
task :mantests do
|
||||
load_manual['sections'].each do |section|
|
||||
(section['entries'] || []).each do |entry|
|
||||
(entry['examples'] || []).each do |example|
|
||||
puts example['program'].gsub("\n", " ")
|
||||
puts example['input']
|
||||
example['output'].each do |s| puts s end
|
||||
puts
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,241 @@
|
||||
headline: Tutorial
|
||||
body:
|
||||
- text: |
|
||||
|
||||
Twitter has a JSON API, so let's play with that. This URL gets
|
||||
us the last 5 tweets about JSON:
|
||||
|
||||
- command: "curl 'http://search.twitter.com/search.json?q=json&rpp=5&include_entities=true'"
|
||||
result: |
|
||||
{"completed_in":0.108,"max_id":247677287108067328,"max_id_str":"247677287108067328","next_page":"?page=2&max_id=247677287108067328&q=json&rpp=5&include_entities=1","page":1,"query":"json","refresh_url":"?since_id=247677287108067328&q=json&include_entities=1","results":[{"created_at":"Mon, 17 Sep 2012 12:44:01 +0000","entities":{"hashtags":[],"urls":[{"url":"http:\/\/t.co\/XRvh1ZVw","expanded_url":"http:\/\/jase.im\/Ri7I0M","display_url":"jase.im\/Ri7I0M","indices":[112,132]}],"user_mentions":[{"screen_name":"imagemechanics","name":"Jason Cotterell","id":57271393,"id_str":"57271393","indices":[3,18]}]},"from_user":"_AaronNg","from_user_id":79771704,"from_user_id_str":"79771704","from_user_name":"NgChenChong","geo":null,"id":247677287108067328,"id_str":"247677287108067328","iso_language_code":"en","metadata":{"result_type":"recent"},"profile_image_url":"http:\/\/a0.twimg.com\/profile_images\/2523558403\/ek8mo4j4beq84iw28gjl_normal.jpeg","profile_image_url_https":"https:\/\/si0.twimg.com\/profile_images\/2523558403\/ek8mo4j4beq84iw28gjl_normal.jpeg","source":"<a href="http:\/\/twitter.com\/">web<\/a>","text":"RT @imagemechanics: iPhone 5 website teardown: How Apple compresses video using JPEG, JSON, and <canvas>.\nhttp:\/\/t.co\/XRvh1ZVw","to_user":null,"to_user_id":0,"to_user_id_str":"0","to_user_name":null}, ...
|
||||
|
||||
- text: |
|
||||
|
||||
There's lots of info and no whitespace, so to make it a bit more
|
||||
legible we pipe it through jq, telling jq to just spit the input
|
||||
back at us using the expression `.`:
|
||||
|
||||
|
||||
- command: "curl 'http://search.twitter.com/search.json?q=json&rpp=5&include_entities=true' | jq '.'"
|
||||
result: |
|
||||
|
||||
{
|
||||
"since_id_str": "0",
|
||||
"since_id": 0,
|
||||
"results_per_page": 5,
|
||||
"completed_in": 0.108,
|
||||
"max_id": 247677287108067330,
|
||||
"max_id_str": "247677287108067328",
|
||||
"next_page": "?page=2&max_id=247677287108067328&q=json&rpp=5&include_entities=1",
|
||||
"page": 1,
|
||||
"query": "json",
|
||||
"refresh_url": "?since_id=247677287108067328&q=json&include_entities=1",
|
||||
"results": [
|
||||
{
|
||||
"from_user_name": "NgChenChong",
|
||||
"from_user_id_str": "79771704",
|
||||
"from_user_id": 79771704,
|
||||
"from_user": "_AaronNg",
|
||||
"iso_language_code": "en",
|
||||
"text": "RT @imagemechanics: iPhone 5 website teardown: How Apple compresses video using JPEG, JSON, and <canvas>.\nhttp://t.co/XRvh1ZVw",
|
||||
"to_user": null
|
||||
/* lots more fields... */
|
||||
},
|
||||
/* lots more results... */
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
- text: |
|
||||
|
||||
Let's pull out the first tweet:
|
||||
|
||||
|
||||
- command: "curl 'http://search.twitter.com/search.json?q=json&rpp=5&include_entities=true' | jq '.results[0]'"
|
||||
result: |
|
||||
{
|
||||
"from_user_name": "NgChenChong",
|
||||
"from_user_id_str": "79771704",
|
||||
"from_user_id": 79771704,
|
||||
"from_user": "_AaronNg",
|
||||
"iso_language_code": "en",
|
||||
"text": "RT @imagemechanics: iPhone 5 website teardown: How Apple compresses video using JPEG, JSON, and <canvas>.\nhttp://t.co/XRvh1ZVw",
|
||||
"to_user": null
|
||||
/* lots more fields... */
|
||||
},
|
||||
|
||||
- text: |
|
||||
|
||||
For the rest of the examples, I'll leave out the `curl` command - it's not going to change.
|
||||
|
||||
|
||||
There's still a lot of info we don't care about there, so we'll
|
||||
restrict it down to the most interesting fields.
|
||||
|
||||
|
||||
- command: "jq '.results[0] | {from_user, text}'"
|
||||
result: |
|
||||
{
|
||||
"text": "RT @imagemechanics: iPhone 5 website teardown: How Apple compresses video using JPEG, JSON, and <canvas>.\nhttp://t.co/XRvh1ZVw",
|
||||
"from_user": "_AaronNg"
|
||||
}
|
||||
|
||||
- text: |
|
||||
|
||||
The `|` operator in jq feeds the output of one filter
|
||||
(`.results[0]` which gets the first element of the results
|
||||
array) into the input of another (`{from_user, text}` which
|
||||
builds an object of those two fields).
|
||||
|
||||
Now let's get the rest of the tweets:
|
||||
|
||||
|
||||
|
||||
- command: "jq '.results[] | {from_user, text}'"
|
||||
result: |
|
||||
{
|
||||
"text": "RT @imagemechanics: iPhone 5 website teardown: How Apple compresses video using JPEG, JSON, and <canvas>.\nhttp://t.co/XRvh1ZVw",
|
||||
"from_user": "_AaronNg"
|
||||
}
|
||||
{
|
||||
"text": "RT @_kud: iPhone 5 website teardown: How Apple compresses video using JPEG, JSON, and <canvas> -- http://t.co/Lhp92IqD",
|
||||
"from_user": "blouerat"
|
||||
}
|
||||
{
|
||||
"text": "Dynamic Forms Mobile App by pmadiset: Few Forms details are hosted on our server in the form of JSON. This JSON ... http://t.co/7KALdWaX",
|
||||
"from_user": "cebu_iphone"
|
||||
}
|
||||
{
|
||||
"text": "iPhone 5 website insanity - Video compressing using JPEG, JSON, and <canvas> http://t.co/28Jesbio (Oh #Apple, U So #Awesome...)",
|
||||
"from_user": "dieswaytoofast"
|
||||
}
|
||||
{
|
||||
"text": "RT @umutm: A very nice web-based JSON editor - http://t.co/M70snaIf",
|
||||
"from_user": "Leolik"
|
||||
}
|
||||
|
||||
|
||||
- text: |
|
||||
|
||||
`.results[]` returns each element of the results array, one at a
|
||||
time, which are all fed into `{from_user, text}`.
|
||||
|
||||
|
||||
Data in jq is represented as streams of JSON values - every jq
|
||||
expression runs for each value in its input stream, and can
|
||||
produce any number of values to its output stream.
|
||||
|
||||
Streams are serialised by just separating JSON values with
|
||||
whitespace. This is a `cat`-friendly format - you can just join
|
||||
two JSON streams together and get a valid JSON stream.
|
||||
|
||||
If you want to get the output as a single array, you can tell jq to
|
||||
"collect" all of the answers by wrapping the filter in square
|
||||
brackets:
|
||||
|
||||
- command: "jq '[.results[] | {from_user, text}]'"
|
||||
result: |
|
||||
[
|
||||
{
|
||||
"text": "RT @imagemechanics: iPhone 5 website teardown: How Apple compresses video using JPEG, JSON, and <canvas>.\nhttp://t.co/XRvh1ZVw",
|
||||
"from_user": "_AaronNg"
|
||||
},
|
||||
{
|
||||
"text": "RT @_kud: iPhone 5 website teardown: How Apple compresses video using JPEG, JSON, and <canvas> -- http://t.co/Lhp92IqD",
|
||||
"from_user": "blouerat"
|
||||
},
|
||||
{
|
||||
"text": "Dynamic Forms Mobile App by pmadiset: Few Forms details are hosted on our server in the form of JSON. This JSON ... http://t.co/7KALdWaX",
|
||||
"from_user": "cebu_iphone"
|
||||
},
|
||||
{
|
||||
"text": "iPhone 5 website insanity - Video compressing using JPEG, JSON, and <canvas> http://t.co/28Jesbio (Oh #Apple, U So #Awesome...)",
|
||||
"from_user": "dieswaytoofast"
|
||||
},
|
||||
{
|
||||
"text": "RT @umutm: A very nice web-based JSON editor - http://t.co/M70snaIf",
|
||||
"from_user": "Leolik"
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
- text: |
|
||||
|
||||
- - -
|
||||
|
||||
Next, let's try getting the URLs out of those API results as
|
||||
well. In each tweet, the Twitter API includes a field called
|
||||
"entities" which looks like this:
|
||||
|
||||
{
|
||||
"user_mentions": [],
|
||||
"urls": [{
|
||||
"indices": [83, 103],
|
||||
"display_url": "bit.ly/StniqT",
|
||||
"expanded_url": "http://bit.ly/StniqT",
|
||||
"url": "http://t.co/28Jesbio"
|
||||
}],
|
||||
"hashtags": [
|
||||
{"indices": [108, 114], "text": "Apple" },
|
||||
{"indices": [121, 129], "text": "Awesome"}
|
||||
]
|
||||
}
|
||||
|
||||
We want to pull out all of the "url" fields inside that array of url
|
||||
objects, and make a simple list of strings to go along with the
|
||||
"from_user" and "text" fields we already have.
|
||||
|
||||
|
||||
|
||||
- command: "jq '.results[] | {from_user, text, urls: [.entities.urls[].url]}'"
|
||||
result: |
|
||||
{
|
||||
"urls": [
|
||||
"http://t.co/XRvh1ZVw"
|
||||
],
|
||||
"text": "RT @imagemechanics: iPhone 5 website teardown: How Apple compresses video using JPEG, JSON, and <canvas>.\nhttp://t.co/XRvh1ZVw",
|
||||
"from_user": "_AaronNg"
|
||||
}
|
||||
{
|
||||
"urls": [
|
||||
"http://t.co/Lhp92IqD"
|
||||
],
|
||||
"text": "RT @_kud: iPhone 5 website teardown: How Apple compresses video using JPEG, JSON, and <canvas> -- http://t.co/Lhp92IqD",
|
||||
"from_user": "blouerat"
|
||||
}
|
||||
{
|
||||
"urls": [
|
||||
"http://t.co/7KALdWaX"
|
||||
],
|
||||
"text": "Dynamic Forms Mobile App by pmadiset: Few Forms details are hosted on our server in the form of JSON. This JSON ... http://t.co/7KALdWaX",
|
||||
"from_user": "cebu_iphone"
|
||||
}
|
||||
{
|
||||
"urls": [
|
||||
"http://t.co/28Jesbio"
|
||||
],
|
||||
"text": "iPhone 5 website insanity - Video compressing using JPEG, JSON, and <canvas> http://t.co/28Jesbio (Oh #Apple, U So #Awesome...)",
|
||||
"from_user": "dieswaytoofast"
|
||||
}
|
||||
{
|
||||
"urls": [
|
||||
"http://t.co/M70snaIf"
|
||||
],
|
||||
"text": "RT @umutm: A very nice web-based JSON editor - http://t.co/M70snaIf",
|
||||
"from_user": "Leolik"
|
||||
}
|
||||
|
||||
|
||||
- text: |
|
||||
|
||||
Here we're making an object as before, but this time the urls
|
||||
field is being set to `[.entities.urls[].url]`, which collects
|
||||
all of the URLs defined in the entities object.
|
||||
|
||||
- text: |
|
||||
|
||||
- - -
|
||||
|
||||
Here endeth the tutorial! There's lots more to play with, go
|
||||
read [the manual](../manual) if you're interested, and [download
|
||||
jq](../download) if you haven't already.
|
||||
@@ -0,0 +1,78 @@
|
||||
headline: Download jq
|
||||
body:
|
||||
|
||||
- text: |
|
||||
|
||||
jq is written in C and has no runtime dependencies, so it should be
|
||||
possible to build it for nearly any platform. Prebuilt binaries are
|
||||
available for Linux, OS X and Windows.
|
||||
|
||||
The binaries should just run, but on OS X and Linux you may need
|
||||
to make them executable first using `chmod +x jq`.
|
||||
|
||||
jq is licensed under the MIT license. For all of the gory
|
||||
details, read the file `COPYING` in the source distribution.
|
||||
|
||||
|
||||
### Linux
|
||||
|
||||
* Binaries for [64-bit](linux64/jq) or [32-bit](linux32/jq) systems.
|
||||
|
||||
* For Arch users, a PKGBUILD for jq-1.1 is in the
|
||||
[AUR](https://aur.archlinux.org/packages.php?ID=63849), as
|
||||
well as a PKGBUILD for the HEAD of master
|
||||
([jq-git](https://aur.archlinux.org/packages.php?ID=63850)).
|
||||
You can install them using
|
||||
[Yaourt](https://wiki.archlinux.org/index.php/Yaourt) or
|
||||
manually by following instructions on [Arch Linux's
|
||||
Wiki](https://wiki.archlinux.org/index.php/Arch_UseRepository).
|
||||
|
||||
* Debian/Ubuntu packages are coming Real Soon Now.
|
||||
|
||||
### OS X
|
||||
|
||||
* `brew install jq` using [homebrew](http://mxcl.github.com/homebrew/)
|
||||
|
||||
* Or, grab prebuilt [64-bit binaries](osx64/jq) or [32-bit
|
||||
binaries](osx32/jq)
|
||||
|
||||
### Windows
|
||||
|
||||
* Executables for [64-bit](win64/jq) or [32-bit](win32/jq)
|
||||
|
||||
### From source on Linux or OS X
|
||||
|
||||
git clone https://github.com/stedolan/jq.git
|
||||
cd jq
|
||||
make && sudo make install
|
||||
|
||||
To build it from a git clone, you'll need to install a few
|
||||
packages first:
|
||||
|
||||
* [Flex](http://www.gnu.org/software/flex/)
|
||||
* [Bison](http://www.gnu.org/software/bison/)
|
||||
* [Python](http://www.python.org)
|
||||
* [GCC](http://gcc.gnu.org)
|
||||
* [Make](http://www.gnu.org/software/make)
|
||||
|
||||
For Linux systems, these will all be in your system's package
|
||||
manager, and if you do development on the machine they're most
|
||||
likely already installed.
|
||||
|
||||
On OS X, these are all included in Apple's command line tools, which
|
||||
can be installed from [Xcode](http://developer.apple.com/technologies/tools/).
|
||||
However, you may find that you need a newer version of Bison than the one provided
|
||||
by Apple. This can be found in [Homebrew](http://mxcl.github.com/homebrew/)
|
||||
or [MacPorts](http://macports.org/).
|
||||
|
||||
`flex` and `bison` are used to generate the lexer and parser for
|
||||
jq, and some python scripts generate the UTF8 encoding tables
|
||||
needed for JSON parsing.
|
||||
|
||||
#### Building the documentation
|
||||
|
||||
jq's documentation is compiled into static HTML using
|
||||
[Bonsai](http://www.tinytree.info). To view the documentation
|
||||
locally, run `rake serve` from the docs/ subdirectory.
|
||||
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,27 @@
|
||||
headline: jq
|
||||
|
||||
blurb: |
|
||||
|
||||
jq is a lightweight and flexible command-line JSON processor.
|
||||
|
||||
body1: |
|
||||
|
||||
jq is like `sed` for JSON data - you can use it to slice and filter
|
||||
and map and transform structured data with the same ease that `sed`,
|
||||
`awk`, `grep` and friends let you play with text.
|
||||
|
||||
body2: |
|
||||
|
||||
jq is written in portable C, and it has zero runtime
|
||||
dependencies. You can download a single binary, `scp` it to a far away
|
||||
machine, and expect it to work.
|
||||
|
||||
body3: |
|
||||
|
||||
jq can mangle the data format that you have into the one that you
|
||||
want with very little effort, and the program to do so is often
|
||||
shorter and simpler than you'd expect.
|
||||
|
||||
tail: |
|
||||
Go read the [tutorial](tutorial) for more, or the [manual](manual)
|
||||
for *way* more.
|
||||
@@ -0,0 +1,28 @@
|
||||
DirectoryIndex index.html
|
||||
FileETag All
|
||||
|
||||
# Compress all static assets
|
||||
<IfModule mod_deflate.c>
|
||||
# compress content with type html, text, and css
|
||||
AddOutputFilterByType DEFLATE text/css text/html text/javascript application/javascript application/x-javascript text/js text/plain text/xml
|
||||
|
||||
<IfModule mod_headers.c>
|
||||
# properly handle requests coming from behind proxies
|
||||
Header append Vary User-Agent
|
||||
</IfModule>
|
||||
</IfModule>
|
||||
|
||||
# Cache, aggressively
|
||||
<IfModule mod_expires.c>
|
||||
ExpiresActive On
|
||||
ExpiresDefault "access plus 10 days"
|
||||
|
||||
<FilesMatch "\.(eot|ttf|otf)$">
|
||||
ExpiresDefault "access plus 10 years"
|
||||
</filesMatch>
|
||||
</IfModule>
|
||||
|
||||
# Mime-types
|
||||
AddType application/vnd.ms-fontobject .eot
|
||||
AddType font/ttf .ttf
|
||||
AddType font/otf .otf
|
||||
+1058
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,9 @@
|
||||
/*!
|
||||
* Bootstrap Responsive v2.1.1
|
||||
*
|
||||
* Copyright 2012 Twitter, Inc
|
||||
* Licensed under the Apache License v2.0
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Designed and built with all the love in the world @twitter by @mdo and @fat.
|
||||
*/.clearfix{*zoom:1}.clearfix:before,.clearfix:after{display:table;line-height:0;content:""}.clearfix:after{clear:both}.hide-text{font:0/0 a;color:transparent;text-shadow:none;background-color:transparent;border:0}.input-block-level{display:block;width:100%;min-height:30px;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.hidden{display:none;visibility:hidden}.visible-phone{display:none!important}.visible-tablet{display:none!important}.hidden-desktop{display:none!important}.visible-desktop{display:inherit!important}@media(min-width:768px) and (max-width:979px){.hidden-desktop{display:inherit!important}.visible-desktop{display:none!important}.visible-tablet{display:inherit!important}.hidden-tablet{display:none!important}}@media(max-width:767px){.hidden-desktop{display:inherit!important}.visible-desktop{display:none!important}.visible-phone{display:inherit!important}.hidden-phone{display:none!important}}@media(min-width:1200px){.row{margin-left:-30px;*zoom:1}.row:before,.row:after{display:table;line-height:0;content:""}.row:after{clear:both}[class*="span"]{float:left;min-height:1px;margin-left:30px}.container,.navbar-static-top .container,.navbar-fixed-top .container,.navbar-fixed-bottom .container{width:1170px}.span12{width:1170px}.span11{width:1070px}.span10{width:970px}.span9{width:870px}.span8{width:770px}.span7{width:670px}.span6{width:570px}.span5{width:470px}.span4{width:370px}.span3{width:270px}.span2{width:170px}.span1{width:70px}.offset12{margin-left:1230px}.offset11{margin-left:1130px}.offset10{margin-left:1030px}.offset9{margin-left:930px}.offset8{margin-left:830px}.offset7{margin-left:730px}.offset6{margin-left:630px}.offset5{margin-left:530px}.offset4{margin-left:430px}.offset3{margin-left:330px}.offset2{margin-left:230px}.offset1{margin-left:130px}.row-fluid{width:100%;*zoom:1}.row-fluid:before,.row-fluid:after{display:table;line-height:0;content:""}.row-fluid:after{clear:both}.row-fluid [class*="span"]{display:block;float:left;width:100%;min-height:30px;margin-left:2.564102564102564%;*margin-left:2.5109110747408616%;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.row-fluid [class*="span"]:first-child{margin-left:0}.row-fluid .span12{width:100%;*width:99.94680851063829%}.row-fluid .span11{width:91.45299145299145%;*width:91.39979996362975%}.row-fluid .span10{width:82.90598290598291%;*width:82.8527914166212%}.row-fluid .span9{width:74.35897435897436%;*width:74.30578286961266%}.row-fluid .span8{width:65.81196581196582%;*width:65.75877432260411%}.row-fluid .span7{width:57.26495726495726%;*width:57.21176577559556%}.row-fluid .span6{width:48.717948717948715%;*width:48.664757228587014%}.row-fluid .span5{width:40.17094017094017%;*width:40.11774868157847%}.row-fluid .span4{width:31.623931623931625%;*width:31.570740134569924%}.row-fluid .span3{width:23.076923076923077%;*width:23.023731587561375%}.row-fluid .span2{width:14.52991452991453%;*width:14.476723040552828%}.row-fluid .span1{width:5.982905982905983%;*width:5.929714493544281%}.row-fluid .offset12{margin-left:105.12820512820512%;*margin-left:105.02182214948171%}.row-fluid .offset12:first-child{margin-left:102.56410256410257%;*margin-left:102.45771958537915%}.row-fluid .offset11{margin-left:96.58119658119658%;*margin-left:96.47481360247316%}.row-fluid .offset11:first-child{margin-left:94.01709401709402%;*margin-left:93.91071103837061%}.row-fluid .offset10{margin-left:88.03418803418803%;*margin-left:87.92780505546462%}.row-fluid .offset10:first-child{margin-left:85.47008547008548%;*margin-left:85.36370249136206%}.row-fluid .offset9{margin-left:79.48717948717949%;*margin-left:79.38079650845607%}.row-fluid .offset9:first-child{margin-left:76.92307692307693%;*margin-left:76.81669394435352%}.row-fluid .offset8{margin-left:70.94017094017094%;*margin-left:70.83378796144753%}.row-fluid .offset8:first-child{margin-left:68.37606837606839%;*margin-left:68.26968539734497%}.row-fluid .offset7{margin-left:62.393162393162385%;*margin-left:62.28677941443899%}.row-fluid .offset7:first-child{margin-left:59.82905982905982%;*margin-left:59.72267685033642%}.row-fluid .offset6{margin-left:53.84615384615384%;*margin-left:53.739770867430444%}.row-fluid .offset6:first-child{margin-left:51.28205128205128%;*margin-left:51.175668303327875%}.row-fluid .offset5{margin-left:45.299145299145295%;*margin-left:45.1927623204219%}.row-fluid .offset5:first-child{margin-left:42.73504273504273%;*margin-left:42.62865975631933%}.row-fluid .offset4{margin-left:36.75213675213675%;*margin-left:36.645753773413354%}.row-fluid .offset4:first-child{margin-left:34.18803418803419%;*margin-left:34.081651209310785%}.row-fluid .offset3{margin-left:28.205128205128204%;*margin-left:28.0987452264048%}.row-fluid .offset3:first-child{margin-left:25.641025641025642%;*margin-left:25.53464266230224%}.row-fluid .offset2{margin-left:19.65811965811966%;*margin-left:19.551736679396257%}.row-fluid .offset2:first-child{margin-left:17.094017094017094%;*maLine truncated
|
||||
+5224
File diff suppressed because it is too large.
Load diff
+9
@@ -0,0 +1,9 @@
|
||||
/*!
|
||||
* Bootstrap v2.1.1
|
||||
*
|
||||
* Copyright 2012 Twitter, Inc
|
||||
* Licensed under the Apache License v2.0
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Designed and built with all the love in the world @twitter by @mdo and @fat.
|
||||
*/article,aside,details,figcaption,figure,footer,header,hgroup,nav,section{display:block}audio,canvas,video{display:inline-block;*display:inline;*zoom:1}audio:not([controls]){display:none}html{font-size:100%;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%}a:focus{outline:thin dotted #333;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}a:hover,a:active{outline:0}sub,sup{position:relative;font-size:75%;line-height:0;vertical-align:baseline}sup{top:-0.5em}sub{bottom:-0.25em}img{width:auto\9;height:auto;max-width:100%;vertical-align:middle;border:0;-ms-interpolation-mode:bicubic}#map_canvas img{max-width:none}button,input,select,textarea{margin:0;font-size:100%;vertical-align:middle}button,input{*overflow:visible;line-height:normal}button::-moz-focus-inner,input::-moz-focus-inner{padding:0;border:0}button,input[type="button"],input[type="reset"],input[type="submit"]{cursor:pointer;-webkit-appearance:button}input[type="search"]{-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box;-webkit-appearance:textfield}input[type="search"]::-webkit-search-decoration,input[type="search"]::-webkit-search-cancel-button{-webkit-appearance:none}textarea{overflow:auto;vertical-align:top}.clearfix{*zoom:1}.clearfix:before,.clearfix:after{display:table;line-height:0;content:""}.clearfix:after{clear:both}.hide-text{font:0/0 a;color:transparent;text-shadow:none;background-color:transparent;border:0}.input-block-level{display:block;width:100%;min-height:30px;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}body{margin:0;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:14px;line-height:20px;color:#333;background-color:#fff}a{color:#08c;text-decoration:none}a:hover{color:#005580;text-decoration:underline}.img-rounded{-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px}.img-polaroid{padding:4px;background-color:#fff;border:1px solid #ccc;border:1px solid rgba(0,0,0,0.2);-webkit-box-shadow:0 1px 3px rgba(0,0,0,0.1);-moz-box-shadow:0 1px 3px rgba(0,0,0,0.1);box-shadow:0 1px 3px rgba(0,0,0,0.1)}.img-circle{-webkit-border-radius:500px;-moz-border-radius:500px;border-radius:500px}.row{margin-left:-20px;*zoom:1}.row:before,.row:after{display:table;line-height:0;content:""}.row:after{clear:both}[class*="span"]{float:left;min-height:1px;margin-left:20px}.container,.navbar-static-top .container,.navbar-fixed-top .container,.navbar-fixed-bottom .container{width:940px}.span12{width:940px}.span11{width:860px}.span10{width:780px}.span9{width:700px}.span8{width:620px}.span7{width:540px}.span6{width:460px}.span5{width:380px}.span4{width:300px}.span3{width:220px}.span2{width:140px}.span1{width:60px}.offset12{margin-left:980px}.offset11{margin-left:900px}.offset10{margin-left:820px}.offset9{margin-left:740px}.offset8{margin-left:660px}.offset7{margin-left:580px}.offset6{margin-left:500px}.offset5{margin-left:420px}.offset4{margin-left:340px}.offset3{margin-left:260px}.offset2{margin-left:180px}.offset1{margin-left:100px}.row-fluid{width:100%;*zoom:1}.row-fluid:before,.row-fluid:after{display:table;line-height:0;content:""}.row-fluid:after{clear:both}.row-fluid [class*="span"]{display:block;float:left;width:100%;min-height:30px;margin-left:2.127659574468085%;*margin-left:2.074468085106383%;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.row-fluid [class*="span"]:first-child{margin-left:0}.row-fluid .span12{width:100%;*width:99.94680851063829%}.row-fluid .span11{width:91.48936170212765%;*width:91.43617021276594%}.row-fluid .span10{width:82.97872340425532%;*width:82.92553191489361%}.row-fluid .span9{width:74.46808510638297%;*width:74.41489361702126%}.row-fluid .span8{width:65.95744680851064%;*width:65.90425531914893%}.row-fluid .span7{width:57.44680851063829%;*width:57.39361702127659%}.row-fluid .span6{width:48.93617021276595%;*width:48.88297872340425%}.row-fluid .span5{width:40.42553191489362%;*width:40.37234042553192%}.row-fluid .span4{width:31.914893617021278%;*width:31.861702127659576%}.row-fluid .span3{width:23.404255319148934%;*width:23.351063829787233%}.row-fluid .span2{width:14.893617021276595%;*width:14.840425531914894%}.row-fluid .span1{width:6.382978723404255%;*width:6.329787234042553%}.row-fluid .offset12{margin-left:104.25531914893617%;*margin-left:104.14893617021275%}.row-fluid .offset12:first-child{margin-left:102.12765957446808%;*margin-left:102.02127659574467%}.row-fluid .offset11{margin-left:95.74468085106382%;*margin-left:95.6382978723404%}.row-fluid .offset11:first-child{margin-left:93.61702127659574%;*margin-left:93.51063829787232%}.row-fluid .offset10{margin-left:87.23404255319149%;*margin-left:87.12765957446807%}.row-fluid .offset10:first-child{margin-left:85.1063829787234%;*margin-left:84.99999999999999%}.row-fluid .offset9{margin-left:78.72340425531914%;*margin-left:78.61702127659572%}.row-fluid .offset9:first-child{margin-left:76.59574468085106%;*margin-left:76.48936170212764%}.row-fluiLine truncated
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 8.6 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 12 KiB |
+2027
File diff suppressed because it is too large.
Load diff
+6
@@ -0,0 +1,6 @@
|
||||
/*!
|
||||
* Bootstrap.js by @fat & @mdo
|
||||
* Copyright 2012 Twitter, Inc.
|
||||
* http://www.apache.org/licenses/LICENSE-2.0.txt
|
||||
*/
|
||||
!function(e){e(function(){"use strict";e.support.transition=function(){var e=function(){var e=document.createElement("bootstrap"),t={WebkitTransition:"webkitTransitionEnd",MozTransition:"transitionend",OTransition:"oTransitionEnd otransitionend",transition:"transitionend"},n;for(n in t)if(e.style[n]!==undefined)return t[n]}();return e&&{end:e}}()})}(window.jQuery),!function(e){"use strict";var t='[data-dismiss="alert"]',n=function(n){e(n).on("click",t,this.close)};n.prototype.close=function(t){function s(){i.trigger("closed").remove()}var n=e(this),r=n.attr("data-target"),i;r||(r=n.attr("href"),r=r&&r.replace(/.*(?=#[^\s]*$)/,"")),i=e(r),t&&t.preventDefault(),i.length||(i=n.hasClass("alert")?n:n.parent()),i.trigger(t=e.Event("close"));if(t.isDefaultPrevented())return;i.removeClass("in"),e.support.transition&&i.hasClass("fade")?i.on(e.support.transition.end,s):s()},e.fn.alert=function(t){return this.each(function(){var r=e(this),i=r.data("alert");i||r.data("alert",i=new n(this)),typeof t=="string"&&i[t].call(r)})},e.fn.alert.Constructor=n,e(function(){e("body").on("click.alert.data-api",t,n.prototype.close)})}(window.jQuery),!function(e){"use strict";var t=function(t,n){this.$element=e(t),this.options=e.extend({},e.fn.button.defaults,n)};t.prototype.setState=function(e){var t="disabled",n=this.$element,r=n.data(),i=n.is("input")?"val":"html";e+="Text",r.resetText||n.data("resetText",n[i]()),n[i](r[e]||this.options[e]),setTimeout(function(){e=="loadingText"?n.addClass(t).attr(t,t):n.removeClass(t).removeAttr(t)},0)},t.prototype.toggle=function(){var e=this.$element.closest('[data-toggle="buttons-radio"]');e&&e.find(".active").removeClass("active"),this.$element.toggleClass("active")},e.fn.button=function(n){return this.each(function(){var r=e(this),i=r.data("button"),s=typeof n=="object"&&n;i||r.data("button",i=new t(this,s)),n=="toggle"?i.toggle():n&&i.setState(n)})},e.fn.button.defaults={loadingText:"loading..."},e.fn.button.Constructor=t,e(function(){e("body").on("click.button.data-api","[data-toggle^=button]",function(t){var n=e(t.target);n.hasClass("btn")||(n=n.closest(".btn")),n.button("toggle")})})}(window.jQuery),!function(e){"use strict";var t=function(t,n){this.$element=e(t),this.options=n,this.options.slide&&this.slide(this.options.slide),this.options.pause=="hover"&&this.$element.on("mouseenter",e.proxy(this.pause,this)).on("mouseleave",e.proxy(this.cycle,this))};t.prototype={cycle:function(t){return t||(this.paused=!1),this.options.interval&&!this.paused&&(this.interval=setInterval(e.proxy(this.next,this),this.options.interval)),this},to:function(t){var n=this.$element.find(".item.active"),r=n.parent().children(),i=r.index(n),s=this;if(t>r.length-1||t<0)return;return this.sliding?this.$element.one("slid",function(){s.to(t)}):i==t?this.pause().cycle():this.slide(t>i?"next":"prev",e(r[t]))},pause:function(t){return t||(this.paused=!0),this.$element.find(".next, .prev").length&&e.support.transition.end&&(this.$element.trigger(e.support.transition.end),this.cycle()),clearInterval(this.interval),this.interval=null,this},next:function(){if(this.sliding)return;return this.slide("next")},prev:function(){if(this.sliding)return;return this.slide("prev")},slide:function(t,n){var r=this.$element.find(".item.active"),i=n||r[t](),s=this.interval,o=t=="next"?"left":"right",u=t=="next"?"first":"last",a=this,f=e.Event("slide",{relatedTarget:i[0]});this.sliding=!0,s&&this.pause(),i=i.length?i:this.$element.find(".item")[u]();if(i.hasClass("active"))return;if(e.support.transition&&this.$element.hasClass("slide")){this.$element.trigger(f);if(f.isDefaultPrevented())return;i.addClass(t),i[0].offsetWidth,r.addClass(o),i.addClass(o),this.$element.one(e.support.transition.end,function(){i.removeClass([t,o].join(" ")).addClass("active"),r.removeClass(["active",o].join(" ")),a.sliding=!1,setTimeout(function(){a.$element.trigger("slid")},0)})}else{this.$element.trigger(f);if(f.isDefaultPrevented())return;r.removeClass("active"),i.addClass("active"),this.sliding=!1,this.$element.trigger("slid")}return s&&this.cycle(),this}},e.fn.carousel=function(n){return this.each(function(){var r=e(this),i=r.data("carousel"),s=e.extend({},e.fn.carousel.defaults,typeof n=="object"&&n),o=typeof n=="string"?n:s.slide;i||r.data("carousel",i=new t(this,s)),typeof n=="number"?i.to(n):o?i[o]():s.interval&&i.cycle()})},e.fn.carousel.defaults={interval:5e3,pause:"hover"},e.fn.carousel.Constructor=t,e(function(){e("body").on("click.carousel.data-api","[data-slide]",function(t){var n=e(this),r,i=e(n.attr("data-target")||(r=n.attr("href"))&&r.replace(/.*(?=#[^\s]+$)/,"")),s=!i.data("modal")&&e.extend({},i.data(),n.data());i.carousel(s),t.preventDefault()})})}(window.jQuery),!function(e){"use strict";var t=function(t,n){this.$element=e(t),this.options=e.extend({},e.fn.collapse.defaults,n),this.options.parent&&(this.$parent=e(this.options.parent)),this.options.toggle&&this.toggle()};t.prototype={constructor:t,dimension:function(){var e=this.$element.hLine truncated
|
||||
@@ -0,0 +1,92 @@
|
||||
@charset "utf-8";
|
||||
body {
|
||||
padding-top: 60px;
|
||||
}
|
||||
|
||||
footer {
|
||||
background-color: #F5F5F5;
|
||||
padding: 40px 0;
|
||||
height: 100px;
|
||||
margin-top: 40px;
|
||||
color: #999999;
|
||||
}
|
||||
|
||||
section {
|
||||
padding-top: 30px;
|
||||
}
|
||||
|
||||
.manual-example {
|
||||
border-top: 1px solid #E5E5E5;
|
||||
}
|
||||
|
||||
.manual-example td {
|
||||
white-space: pre-wrap;
|
||||
font-family: Monaco, Menlo, Consolas, "Courier New", monospace;
|
||||
}
|
||||
|
||||
.manual-example td.jqprogram {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.manual-example th {
|
||||
text-align: right;
|
||||
padding-right: 10px;
|
||||
}
|
||||
|
||||
.tutorial-example {
|
||||
position: relative;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.tutorial-example pre {
|
||||
margin-bottom: 0px;
|
||||
}
|
||||
|
||||
.tutorial-example a {
|
||||
position: absolute;
|
||||
top: 0px;
|
||||
right: 0px;
|
||||
padding: 15px 8px;
|
||||
color: #777777;
|
||||
font-weight: bold;
|
||||
line-height: 10px;
|
||||
font-size: 12px;
|
||||
border-left: 1px solid #DDDDDD;
|
||||
bottom: 0;
|
||||
display: block;
|
||||
}
|
||||
|
||||
|
||||
|
||||
#blurb {
|
||||
padding-top: 40px;
|
||||
padding-right: 50px;
|
||||
}
|
||||
|
||||
#blurb p {
|
||||
font-size: 20pt;
|
||||
}
|
||||
|
||||
#blurb .btn-group {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
#multiblurb {
|
||||
line-height: 1.7;
|
||||
text-align: center;
|
||||
font-size: 12pt;
|
||||
}
|
||||
|
||||
#multiblurb code {
|
||||
border: 0;
|
||||
font-size: 12pt;
|
||||
}
|
||||
|
||||
h3 code {
|
||||
border: 0;
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
footer {
|
||||
text-align: center;
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 8.0 KiB |
@@ -0,0 +1,2 @@
|
||||
User-agent: *
|
||||
Allow: /
|
||||
@@ -0,0 +1,14 @@
|
||||
# The key value pairs found below are available within the templates.
|
||||
|
||||
|
||||
jq_version: 1.2
|
||||
root: '/jq'
|
||||
footer: |
|
||||
|
||||
This website is made with [Bonsai](http://www.tinytree.info) and
|
||||
[Twitter Bootstrap](http://twitter.github.com/bootstrap/), themed
|
||||
with [Bootswatch](http://bootswatch.com).
|
||||
|
||||
jq is licensed under the MIT license (code) and the
|
||||
[CC-BY-3.0](http://creativecommons.org/licenses/by/3.0/) license
|
||||
(docs).
|
||||
Vendored
+34
@@ -0,0 +1,34 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
{% include "shared/head" %}
|
||||
<body id="{{slug}}">
|
||||
{% include "shared/header" %}
|
||||
|
||||
<div class="container">
|
||||
|
||||
<div class="row">
|
||||
<h1>{{headline}}</h1>
|
||||
{% for item in body %}
|
||||
{% if item.text %}
|
||||
{{ item.text | markdownify }}
|
||||
{% endif %}
|
||||
|
||||
{% if item.command %}
|
||||
|
||||
|
||||
{% capture resultID %}{{ "result" | unique}}{% endcapture %}
|
||||
<div class="tutorial-example">
|
||||
<div class="accordion-heading">
|
||||
<pre>{{item.command}}</pre>
|
||||
<a data-toggle="collapse" href="#{{resultID}}">Show result</a>
|
||||
</div>
|
||||
<div id="{{resultID}}" class="accordion-body collapse"><pre>{{item.result}}</pre></div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
{% include "shared/footer" %}
|
||||
|
||||
</body>
|
||||
</html>
|
||||
Vendored
+41
@@ -0,0 +1,41 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
{% include "shared/head" %}
|
||||
<body id="{{slug}}">
|
||||
{% include "shared/header" %}
|
||||
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="hero-unit">
|
||||
<img src="{{root}}/jq.png" style="width: 400px; height: 220px; float: left; margin-right: 60px">
|
||||
<div id="blurb">
|
||||
{{blurb | markdownify}}
|
||||
<div class="btn-group" style="float:right">
|
||||
<a class="btn btn-primary dropdown-toggle" data-toggle="dropdown" href="#">
|
||||
Download jq-{{jq_version}}
|
||||
<span class="caret"></span>
|
||||
</a>
|
||||
<ul class="dropdown-menu">
|
||||
<li><a href="{{root}}/download/linux64/jq">Linux (64-bit)</a></li>
|
||||
<li><a href="{{root}}/download/osx64/jq">OS X (64-bit)</a></li>
|
||||
<li><a href="{{root}}/download">Other platforms and source</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div style="clear:both;"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row" id="multiblurb">
|
||||
<div class="span4">{{body1 | markdownify}}</div>
|
||||
<div class="span4">{{body2 | markdownify}}</div>
|
||||
<div class="span4">{{body3 | markdownify}}</div>
|
||||
</div>
|
||||
<div class="row" style="text-align:center; margin-top: 30px">
|
||||
{{tail | markdownify}}
|
||||
</div>
|
||||
</div>
|
||||
{% include "shared/footer" %}
|
||||
</body>
|
||||
</html>
|
||||
Vendored
+110
@@ -0,0 +1,110 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
{% include "shared/head" %}
|
||||
<body id="{{slug}}" data-spy="scroll" data-target="#navcolumn" data-offset="100">
|
||||
{% include "shared/header" %}
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="span3" id="navcolumn">
|
||||
<div class="affix">
|
||||
<h4>Contents</h4>
|
||||
<ul class="nav nav-pills nav-stacked">
|
||||
{% for section in sections %}
|
||||
<li {% if forloop.first %} class="active" {% endif %}>
|
||||
<a href="#{{section.title | sanitize}}">{{section.title}}</a>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
<form class="form-search">
|
||||
<script type="text/javascript">
|
||||
var section_map = {
|
||||
{% for section in sections %}
|
||||
{% for entry in section.entries %}
|
||||
{{entry.title | json}} : {{entry.title | sanitize | json}},
|
||||
{% endfor %}
|
||||
{{section.title | json}} : {{section.title | sanitize | json}}
|
||||
{% unless forloop.last %},{% endunless %}
|
||||
{% endfor %}
|
||||
};
|
||||
var section_names = $.map(section_map, function(v,k){return k});
|
||||
$(function(){
|
||||
$('#searchbox').typeahead({source: section_names})
|
||||
$('#searchbox').change(function() {
|
||||
if ($(this).val() in section_map) {
|
||||
location.hash = '#' + section_map[$(this).val()];
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
<input type="text"
|
||||
class="input-medium search-query"
|
||||
placeholder="Search"
|
||||
autocomplete="off"
|
||||
id="searchbox">
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<div class="span9">
|
||||
<h1>{{headline}}</h1>
|
||||
{{ body | markdownify }}
|
||||
{% for section in sections %}
|
||||
<section id="{{section.title | sanitize}}">
|
||||
<h2>{{section.title}}</h2>
|
||||
{{section.body | markdownify}}
|
||||
{% for entry in section.entries %}
|
||||
<section id="{{entry.title | sanitize}}">
|
||||
<h3>
|
||||
{{entry.title | markdownify}}
|
||||
{% if entry.subtitle %}<small>{{entry.subtitle}}</small>{% endif %}
|
||||
</h3>
|
||||
{{entry.body | markdownify}}
|
||||
|
||||
{% if entry.examples %}
|
||||
<div>
|
||||
{% capture exampleID %}{{ "" | unique }}{% endcapture %}
|
||||
<a data-toggle="collapse" href="#example{{exampleID}}">
|
||||
<i class="icon-chevron-right"></i>
|
||||
{% if entry.examples[1] %}Examples{%else%}Example{%endif%}
|
||||
</a>
|
||||
<div id="example{{exampleID}}" class="collapse">
|
||||
|
||||
{% for example in entry.examples %}
|
||||
<table class="manual-example">
|
||||
<tr><th></th><td class="jqprogram">jq '{{example.program}}'</td></tr>
|
||||
<tr><th>Input</th><td>{{example.input}}</td></tr>
|
||||
{% unless example.output[0] %}
|
||||
<tr>
|
||||
<th>Output</th>
|
||||
<td><i>none</i></td>
|
||||
</tr>
|
||||
{% endunless %}
|
||||
{% for output in example.output %}
|
||||
<tr>
|
||||
{% if forloop.index == 1 %}
|
||||
<th>Output</th>
|
||||
{% else %}
|
||||
<th></th>
|
||||
{% endif %}
|
||||
<td>{{output}}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
|
||||
</table>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</section>
|
||||
{% endfor %}
|
||||
</section>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
{% include "shared/footer" %}
|
||||
</body>
|
||||
|
||||
|
||||
</html>
|
||||
Vendored
+5
@@ -0,0 +1,5 @@
|
||||
<footer>
|
||||
<div class="container">
|
||||
{{ footer | markdownify }}
|
||||
</div>
|
||||
</footer>
|
||||
Vendored
+12
@@ -0,0 +1,12 @@
|
||||
<head>
|
||||
<meta charset=UTF-8>
|
||||
<title>{{headline}}</title>
|
||||
<link rel="stylesheet" href="{{root}}/bootstrap/css/bootstrap.css" type="text/css">
|
||||
<link rel="stylesheet" href="{{root}}/css/base.css" type="text/css">
|
||||
<!--[if IE]>
|
||||
<script src="http://html5shiv.googlecode.com/svn/trunk/html5.js"></script>
|
||||
<![endif]-->
|
||||
<script src="//ajax.googleapis.com/ajax/libs/jquery/1.8.1/jquery.min.js"></script>
|
||||
<script src="{{root}}/bootstrap/js/bootstrap.js"></script>
|
||||
|
||||
</head>
|
||||
Vendored
+24
@@ -0,0 +1,24 @@
|
||||
|
||||
<div class="navbar navbar-inverse navbar-fixed-top">
|
||||
<div class="navbar-inner">
|
||||
<div class="container">
|
||||
<button type="button" class="btn btn-navbar" data-toggle="collapse" data-target=".nav-collapse">
|
||||
<span class="icon-bar"></span>
|
||||
<span class="icon-bar"></span>
|
||||
<span class="icon-bar"></span>
|
||||
</button>
|
||||
<a class="brand" href="{{root}}/">jq</a>
|
||||
<div class="nav-collapse collapse">
|
||||
<ul class="nav">
|
||||
{% for item in navigation %}
|
||||
<li {% if item.permalink == permalink %} class="active" {% endif %}>
|
||||
<a href="{{root}}{{item.permalink}}">{{item.name}}</a>
|
||||
</li>
|
||||
{% endfor %}
|
||||
<li><a href="https://github.com/stedolan/jq/issues">Issues</a></li>
|
||||
<li><a href="https://github.com/stedolan/jq">Source</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,515 @@
|
||||
#include <assert.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include "execute.h"
|
||||
|
||||
#include "opcode.h"
|
||||
#include "bytecode.h"
|
||||
|
||||
#include "forkable_stack.h"
|
||||
#include "frame_layout.h"
|
||||
|
||||
#include "jv_alloc.h"
|
||||
#include "locfile.h"
|
||||
#include "jv.h"
|
||||
#include "jv_aux.h"
|
||||
#include "parser.h"
|
||||
#include "builtin.h"
|
||||
|
||||
typedef struct {
|
||||
jv value;
|
||||
int pathidx;
|
||||
} stackval;
|
||||
|
||||
|
||||
jv* pathbuf;
|
||||
int pathsize; // number of allocated elements
|
||||
|
||||
int path_push(stackval sv, jv val) {
|
||||
int pos = sv.pathidx;
|
||||
assert(pos <= pathsize);
|
||||
assert(pos >= 0);
|
||||
if (pos == pathsize) {
|
||||
int oldpathsize = pathsize;
|
||||
pathsize = oldpathsize ? oldpathsize * 2 : 100;
|
||||
pathbuf = jv_mem_realloc(pathbuf, sizeof(pathbuf[0]) * pathsize);
|
||||
for (int i=oldpathsize; i<pathsize; i++) {
|
||||
pathbuf[i] = jv_invalid();
|
||||
}
|
||||
}
|
||||
jv_free(pathbuf[pos]);
|
||||
pathbuf[pos] = val;
|
||||
return pos + 1;
|
||||
}
|
||||
|
||||
stackval stackval_replace(stackval value, jv newjs) {
|
||||
jv_free(value.value);
|
||||
stackval s = {newjs, value.pathidx};
|
||||
return s;
|
||||
}
|
||||
|
||||
|
||||
// Probably all uses of this function are bugs
|
||||
stackval stackval_root(jv v) {
|
||||
stackval s = {v, 0};
|
||||
return s;
|
||||
}
|
||||
|
||||
struct forkable_stack data_stk;
|
||||
typedef struct {
|
||||
FORKABLE_STACK_HEADER;
|
||||
stackval sv;
|
||||
} data_stk_elem;
|
||||
|
||||
void stack_push(stackval val) {
|
||||
assert(jv_is_valid(val.value));
|
||||
data_stk_elem* s = forkable_stack_push(&data_stk, sizeof(data_stk_elem));
|
||||
s->sv = val;
|
||||
}
|
||||
|
||||
stackval stack_pop() {
|
||||
data_stk_elem* s = forkable_stack_peek(&data_stk);
|
||||
stackval sv = s->sv;
|
||||
if (!forkable_stack_pop_will_free(&data_stk)) {
|
||||
sv.value = jv_copy(sv.value);
|
||||
}
|
||||
forkable_stack_pop(&data_stk);
|
||||
assert(jv_is_valid(sv.value));
|
||||
return sv;
|
||||
}
|
||||
|
||||
struct forkable_stack frame_stk;
|
||||
|
||||
|
||||
struct forkpoint {
|
||||
FORKABLE_STACK_HEADER;
|
||||
struct forkable_stack_state saved_data_stack;
|
||||
struct forkable_stack_state saved_call_stack;
|
||||
};
|
||||
|
||||
struct forkable_stack fork_stk;
|
||||
|
||||
void stack_save(){
|
||||
struct forkpoint* fork = forkable_stack_push(&fork_stk, sizeof(struct forkpoint));
|
||||
forkable_stack_save(&data_stk, &fork->saved_data_stack);
|
||||
forkable_stack_save(&frame_stk, &fork->saved_call_stack);
|
||||
}
|
||||
|
||||
void stack_switch() {
|
||||
struct forkpoint* fork = forkable_stack_peek(&fork_stk);
|
||||
forkable_stack_switch(&data_stk, &fork->saved_data_stack);
|
||||
forkable_stack_switch(&frame_stk, &fork->saved_call_stack);
|
||||
}
|
||||
|
||||
int stack_restore(){
|
||||
while (!forkable_stack_empty(&data_stk) &&
|
||||
forkable_stack_pop_will_free(&data_stk)) {
|
||||
jv_free(stack_pop().value);
|
||||
}
|
||||
while (!forkable_stack_empty(&frame_stk) &&
|
||||
forkable_stack_pop_will_free(&frame_stk)) {
|
||||
frame_pop(&frame_stk);
|
||||
}
|
||||
if (forkable_stack_empty(&fork_stk)) {
|
||||
return 0;
|
||||
} else {
|
||||
struct forkpoint* fork = forkable_stack_peek(&fork_stk);
|
||||
forkable_stack_restore(&data_stk, &fork->saved_data_stack);
|
||||
forkable_stack_restore(&frame_stk, &fork->saved_call_stack);
|
||||
forkable_stack_pop(&fork_stk);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
static struct closure make_closure(struct forkable_stack* stk, frame_ptr fr, uint16_t* pc) {
|
||||
uint16_t level = *pc++;
|
||||
uint16_t idx = *pc++;
|
||||
fr = frame_get_level(stk, fr, level);
|
||||
if (idx & ARG_NEWCLOSURE) {
|
||||
int subfn_idx = idx & ~ARG_NEWCLOSURE;
|
||||
assert(subfn_idx < frame_self(fr)->bc->nsubfunctions);
|
||||
struct closure cl = {frame_self(fr)->bc->subfunctions[subfn_idx],
|
||||
forkable_stack_to_idx(stk, fr)};
|
||||
return cl;
|
||||
} else {
|
||||
return *frame_closure_arg(fr, idx);
|
||||
}
|
||||
}
|
||||
|
||||
void print_error(jv value) {
|
||||
assert(!jv_is_valid(value));
|
||||
jv msg = jv_invalid_get_msg(value);
|
||||
if (jv_get_kind(msg) == JV_KIND_STRING) {
|
||||
fprintf(stderr, "jq: error: %s\n", jv_string_value(msg));
|
||||
}
|
||||
jv_free(msg);
|
||||
}
|
||||
#define ON_BACKTRACK(op) ((op)+NUM_OPCODES)
|
||||
|
||||
jv jq_next() {
|
||||
jv cfunc_input[MAX_CFUNCTION_ARGS];
|
||||
|
||||
assert(!forkable_stack_empty(&frame_stk));
|
||||
uint16_t* pc = *frame_current_retaddr(&frame_stk);
|
||||
frame_pop(&frame_stk);
|
||||
|
||||
assert(!forkable_stack_empty(&frame_stk));
|
||||
|
||||
int backtracking = 0;
|
||||
while (1) {
|
||||
uint16_t opcode = *pc;
|
||||
|
||||
#if JQ_DEBUG
|
||||
dump_operation(frame_current_bytecode(&frame_stk), pc);
|
||||
printf("\t");
|
||||
const struct opcode_description* opdesc = opcode_describe(opcode);
|
||||
data_stk_elem* param;
|
||||
int stack_in = opdesc->stack_in;
|
||||
if (stack_in == -1) stack_in = pc[1];
|
||||
for (int i=0; i<stack_in; i++) {
|
||||
if (i == 0) {
|
||||
param = forkable_stack_peek(&data_stk);
|
||||
} else {
|
||||
printf(" | ");
|
||||
param = forkable_stack_peek_next(&data_stk, param);
|
||||
}
|
||||
if (!param) break;
|
||||
jv_dump(jv_copy(param->sv.value), 0);
|
||||
printf("<%d>", jv_get_refcnt(param->sv.value));
|
||||
}
|
||||
|
||||
if (backtracking) printf("\t<backtracking>");
|
||||
|
||||
printf("\n");
|
||||
#endif
|
||||
if (backtracking) {
|
||||
opcode = ON_BACKTRACK(opcode);
|
||||
backtracking = 0;
|
||||
}
|
||||
pc++;
|
||||
|
||||
switch (opcode) {
|
||||
default: assert(0 && "invalid instruction");
|
||||
|
||||
case LOADK: {
|
||||
jv v = jv_array_get(jv_copy(frame_current_bytecode(&frame_stk)->constants), *pc++);
|
||||
assert(jv_is_valid(v));
|
||||
stack_push(stackval_replace(stack_pop(), v));
|
||||
break;
|
||||
}
|
||||
|
||||
case DUP: {
|
||||
stackval v = stack_pop();
|
||||
stackval v2 = v;
|
||||
v2.value = jv_copy(v.value);
|
||||
stack_push(v);
|
||||
stack_push(v2);
|
||||
break;
|
||||
}
|
||||
|
||||
case DUP2: {
|
||||
stackval keep = stack_pop();
|
||||
stackval v = stack_pop();
|
||||
stackval v2 = v;
|
||||
v2.value = jv_copy(v.value);
|
||||
stack_push(v);
|
||||
stack_push(keep);
|
||||
stack_push(v2);
|
||||
break;
|
||||
}
|
||||
|
||||
case SWAP: {
|
||||
stackval a = stack_pop();
|
||||
stackval b = stack_pop();
|
||||
stack_push(a);
|
||||
stack_push(b);
|
||||
break;
|
||||
}
|
||||
|
||||
case POP: {
|
||||
jv_free(stack_pop().value);
|
||||
break;
|
||||
}
|
||||
|
||||
case APPEND: {
|
||||
// FIXME paths
|
||||
jv v = stack_pop().value;
|
||||
jv array = stack_pop().value;
|
||||
array = jv_array_append(array, v);
|
||||
stack_push(stackval_root(array));
|
||||
break;
|
||||
}
|
||||
|
||||
case INSERT: {
|
||||
stackval stktop = stack_pop();
|
||||
jv v = stack_pop().value;
|
||||
jv k = stack_pop().value;
|
||||
stackval objv = stack_pop();
|
||||
assert(jv_get_kind(k) == JV_KIND_STRING);
|
||||
assert(jv_get_kind(objv.value) == JV_KIND_OBJECT);
|
||||
objv.value = jv_object_set(objv.value, k, v);
|
||||
stack_push(objv);
|
||||
stack_push(stktop);
|
||||
break;
|
||||
}
|
||||
|
||||
// FIXME: loadv/storev may do too much copying/freeing
|
||||
case LOADV: {
|
||||
uint16_t level = *pc++;
|
||||
uint16_t v = *pc++;
|
||||
frame_ptr fp = frame_get_level(&frame_stk, frame_current(&frame_stk), level);
|
||||
jv* var = frame_local_var(fp, v);
|
||||
#if JQ_DEBUG
|
||||
printf("V%d = ", v);
|
||||
jv_dump(jv_copy(*var), 0);
|
||||
printf("\n");
|
||||
#endif
|
||||
stack_push(stackval_replace(stack_pop(), jv_copy(*var)));
|
||||
break;
|
||||
}
|
||||
|
||||
case STOREV: {
|
||||
uint16_t level = *pc++;
|
||||
uint16_t v = *pc++;
|
||||
frame_ptr fp = frame_get_level(&frame_stk, frame_current(&frame_stk), level);
|
||||
jv* var = frame_local_var(fp, v);
|
||||
stackval val = stack_pop();
|
||||
#if JQ_DEBUG
|
||||
printf("V%d = ", v);
|
||||
jv_dump(jv_copy(val.value), 0);
|
||||
printf("\n");
|
||||
#endif
|
||||
jv_free(*var);
|
||||
*var = val.value;
|
||||
break;
|
||||
}
|
||||
|
||||
case ASSIGN: {
|
||||
stackval replacement = stack_pop();
|
||||
stackval path_end = stack_pop();
|
||||
stackval path_start = stack_pop();
|
||||
jv_free(path_end.value);
|
||||
jv_free(path_start.value);
|
||||
|
||||
uint16_t level = *pc++;
|
||||
uint16_t v = *pc++;
|
||||
frame_ptr fp = frame_get_level(&frame_stk, frame_current(&frame_stk), level);
|
||||
jv* var = frame_local_var(fp, v);
|
||||
jv result = jv_insert(*var, replacement.value, pathbuf + path_start.pathidx, path_end.pathidx - path_start.pathidx);
|
||||
if (jv_is_valid(result)) {
|
||||
*var = result;
|
||||
} else {
|
||||
print_error(result);
|
||||
*var = jv_null();
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case INDEX: {
|
||||
stackval t = stack_pop();
|
||||
jv k = stack_pop().value;
|
||||
int pathidx = path_push(t, jv_copy(k));
|
||||
jv v = jv_lookup(t.value, k);
|
||||
if (jv_is_valid(v)) {
|
||||
stackval sv;
|
||||
sv.value = v;
|
||||
sv.pathidx = pathidx;
|
||||
stack_push(sv);
|
||||
} else {
|
||||
print_error(v);
|
||||
goto do_backtrack;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
case JUMP: {
|
||||
uint16_t offset = *pc++;
|
||||
pc += offset;
|
||||
break;
|
||||
}
|
||||
|
||||
case JUMP_F: {
|
||||
uint16_t offset = *pc++;
|
||||
stackval t = stack_pop();
|
||||
jv_kind kind = jv_get_kind(t.value);
|
||||
if (kind == JV_KIND_FALSE || kind == JV_KIND_NULL) {
|
||||
pc += offset;
|
||||
}
|
||||
stack_push(t); // FIXME do this better
|
||||
break;
|
||||
}
|
||||
|
||||
case EACH:
|
||||
stack_push(stackval_root(jv_number(-1)));
|
||||
// fallthrough
|
||||
case ON_BACKTRACK(EACH): {
|
||||
int idx = jv_number_value(stack_pop().value);
|
||||
stackval container = stack_pop();
|
||||
|
||||
int keep_going;
|
||||
jv key, value;
|
||||
if (jv_get_kind(container.value) == JV_KIND_ARRAY) {
|
||||
if (opcode == EACH) idx = 0;
|
||||
else idx = idx + 1;
|
||||
keep_going = idx < jv_array_length(jv_copy(container.value));
|
||||
if (keep_going) {
|
||||
key = jv_number(idx);
|
||||
value = jv_array_get(jv_copy(container.value), idx);
|
||||
}
|
||||
} else if (jv_get_kind(container.value) == JV_KIND_OBJECT) {
|
||||
if (opcode == EACH) idx = jv_object_iter(container.value);
|
||||
else idx = jv_object_iter_next(container.value, idx);
|
||||
keep_going = jv_object_iter_valid(container.value, idx);
|
||||
if (keep_going) {
|
||||
key = jv_object_iter_key(container.value, idx);
|
||||
value = jv_object_iter_value(container.value, idx);
|
||||
}
|
||||
} else {
|
||||
assert(opcode == EACH);
|
||||
print_error(jv_invalid_with_msg(jv_string_fmt("Cannot iterate over %s",
|
||||
jv_kind_name(jv_get_kind(container.value)))));
|
||||
keep_going = 0;
|
||||
}
|
||||
|
||||
if (!keep_going) {
|
||||
jv_free(container.value);
|
||||
goto do_backtrack;
|
||||
} else {
|
||||
stack_save();
|
||||
stack_push(container);
|
||||
stack_push(stackval_root(jv_number(idx)));
|
||||
frame_push_backtrack(&frame_stk, pc - 1);
|
||||
stack_switch();
|
||||
|
||||
stackval sv = {value,
|
||||
path_push(container, key)};
|
||||
stack_push(sv);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
do_backtrack:
|
||||
case BACKTRACK: {
|
||||
if (!stack_restore()) {
|
||||
return jv_invalid();
|
||||
}
|
||||
pc = *frame_current_retaddr(&frame_stk);
|
||||
frame_pop(&frame_stk);
|
||||
backtracking = 1;
|
||||
break;
|
||||
}
|
||||
|
||||
case FORK: {
|
||||
stack_save();
|
||||
frame_push_backtrack(&frame_stk, pc - 1);
|
||||
stack_switch();
|
||||
pc++; // skip offset this time
|
||||
break;
|
||||
}
|
||||
|
||||
case ON_BACKTRACK(FORK): {
|
||||
uint16_t offset = *pc++;
|
||||
pc += offset;
|
||||
break;
|
||||
}
|
||||
|
||||
case YIELD: {
|
||||
jv value = stack_pop().value;
|
||||
frame_push_backtrack(&frame_stk, pc);
|
||||
return value;
|
||||
}
|
||||
|
||||
case CALL_BUILTIN: {
|
||||
int nargs = *pc++;
|
||||
stackval top = stack_pop();
|
||||
cfunc_input[0] = top.value;
|
||||
for (int i = 1; i < nargs; i++) {
|
||||
cfunc_input[i] = stack_pop().value;
|
||||
}
|
||||
struct cfunction* func = &frame_current_bytecode(&frame_stk)->globals->cfunctions[*pc++];
|
||||
top.value = cfunction_invoke(func, cfunc_input);
|
||||
if (jv_is_valid(top.value)) {
|
||||
stack_push(top);
|
||||
} else {
|
||||
print_error(top.value);
|
||||
goto do_backtrack;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case CALL_JQ: {
|
||||
uint16_t nclosures = *pc++;
|
||||
uint16_t* retaddr = pc + 2 + nclosures*2;
|
||||
frame_ptr new_frame = frame_push(&frame_stk,
|
||||
make_closure(&frame_stk, frame_current(&frame_stk), pc),
|
||||
retaddr);
|
||||
pc += 2;
|
||||
frame_ptr old_frame = forkable_stack_peek_next(&frame_stk, new_frame);
|
||||
assert(nclosures == frame_self(new_frame)->bc->nclosures);
|
||||
for (int i=0; i<nclosures; i++) {
|
||||
*frame_closure_arg(new_frame, i) = make_closure(&frame_stk, old_frame, pc);
|
||||
pc += 2;
|
||||
}
|
||||
|
||||
pc = frame_current_bytecode(&frame_stk)->code;
|
||||
break;
|
||||
}
|
||||
|
||||
case RET: {
|
||||
pc = *frame_current_retaddr(&frame_stk);
|
||||
frame_pop(&frame_stk);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void jq_init(struct bytecode* bc, jv input) {
|
||||
forkable_stack_init(&data_stk, sizeof(stackval) * 100);
|
||||
forkable_stack_init(&frame_stk, 1024);
|
||||
forkable_stack_init(&fork_stk, 1024);
|
||||
|
||||
stack_push(stackval_root(input));
|
||||
struct closure top = {bc, -1};
|
||||
frame_push(&frame_stk, top, 0);
|
||||
frame_push_backtrack(&frame_stk, bc->code);
|
||||
}
|
||||
|
||||
void jq_teardown() {
|
||||
while (stack_restore()) {}
|
||||
|
||||
assert(forkable_stack_empty(&fork_stk));
|
||||
assert(forkable_stack_empty(&data_stk));
|
||||
assert(forkable_stack_empty(&frame_stk));
|
||||
forkable_stack_free(&fork_stk);
|
||||
forkable_stack_free(&data_stk);
|
||||
forkable_stack_free(&frame_stk);
|
||||
|
||||
for (int i=0; i<pathsize; i++) {
|
||||
jv_free(pathbuf[i]);
|
||||
}
|
||||
jv_mem_free(pathbuf);
|
||||
pathbuf = 0;
|
||||
pathsize = 0;
|
||||
}
|
||||
|
||||
struct bytecode* jq_compile(const char* str) {
|
||||
struct locfile locations;
|
||||
locfile_init(&locations, str, strlen(str));
|
||||
block program;
|
||||
struct bytecode* bc = 0;
|
||||
int nerrors = jq_parse(&locations, &program);
|
||||
if (nerrors == 0) {
|
||||
program = builtins_bind(program);
|
||||
nerrors = block_compile(program, &locations, &bc);
|
||||
}
|
||||
if (nerrors) {
|
||||
fprintf(stderr, "%d compile %s\n", nerrors, nerrors > 1 ? "errors" : "error");
|
||||
}
|
||||
locfile_free(&locations);
|
||||
return bc;
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
#ifndef EXECUTE_H
|
||||
#define EXECUTE_H
|
||||
#include "bytecode.h"
|
||||
|
||||
|
||||
struct bytecode* jq_compile(const char* str);
|
||||
|
||||
void jq_init(struct bytecode* bc, jv value);
|
||||
jv jq_next();
|
||||
void jq_teardown();
|
||||
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,163 @@
|
||||
#ifndef FORKABLE_STACK_H
|
||||
#define FORKABLE_STACK_H
|
||||
#include <stddef.h>
|
||||
#include <assert.h>
|
||||
#include <string.h>
|
||||
#include "jv_alloc.h"
|
||||
|
||||
struct forkable_stack_header {
|
||||
/* distance in bytes between this header and the header
|
||||
of the next object on the stack */
|
||||
int next_delta;
|
||||
};
|
||||
|
||||
#define FORKABLE_STACK_HEADER struct forkable_stack_header fk_header_
|
||||
|
||||
struct forkable_stack {
|
||||
// Stack grows down from stk+length
|
||||
char* stk;
|
||||
|
||||
// stk+length is just past end of allocated area
|
||||
// stk = jv_mem_alloc(length)
|
||||
int length;
|
||||
|
||||
// stk+pos is header of top object, or stk+length if empty
|
||||
int pos;
|
||||
|
||||
// everything after-or-including stk+savedlimit must be preserved
|
||||
int savedlimit;
|
||||
};
|
||||
|
||||
static void forkable_stack_check(struct forkable_stack* s) {
|
||||
assert(s->stk);
|
||||
assert(s->length > 0);
|
||||
assert(s->pos >= 0 && s->pos <= s->length);
|
||||
assert(s->savedlimit >= 0 && s->savedlimit <= s->length);
|
||||
}
|
||||
|
||||
static int forkable_stack_empty(struct forkable_stack* s) {
|
||||
return s->pos == s->length;
|
||||
}
|
||||
|
||||
static void forkable_stack_init(struct forkable_stack* s, size_t sz) {
|
||||
s->stk = jv_mem_alloc(sz);
|
||||
s->length = sz;
|
||||
s->pos = s->length;
|
||||
s->savedlimit = s->length;
|
||||
forkable_stack_check(s);
|
||||
}
|
||||
|
||||
static void forkable_stack_free(struct forkable_stack* s) {
|
||||
assert(forkable_stack_empty(s));
|
||||
assert(s->savedlimit == s->length);
|
||||
jv_mem_free(s->stk);
|
||||
s->stk = 0;
|
||||
}
|
||||
|
||||
static void* forkable_stack_push(struct forkable_stack* s, size_t sz_size) {
|
||||
assert(sz_size > 0 && sz_size % sizeof(struct forkable_stack_header) == 0);
|
||||
int size = (int)sz_size;
|
||||
forkable_stack_check(s);
|
||||
int curr = s->pos < s->savedlimit ? s->pos : s->savedlimit;
|
||||
if (curr - size < 0) {
|
||||
int oldlen = s->length;
|
||||
s->length = (size + oldlen + 1024) * 2;
|
||||
s->stk = jv_mem_realloc(s->stk, s->length);
|
||||
int shift = s->length - oldlen;
|
||||
memmove(s->stk + shift, s->stk, oldlen);
|
||||
s->pos += shift;
|
||||
s->savedlimit += shift;
|
||||
curr += shift;
|
||||
}
|
||||
int newpos = curr - size;
|
||||
assert(newpos < s->pos);
|
||||
void* ret = (void*)(s->stk + newpos);
|
||||
((struct forkable_stack_header*)ret)->next_delta = s->pos - newpos;
|
||||
s->pos = newpos;
|
||||
forkable_stack_check(s);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void* forkable_stack_peek(struct forkable_stack* s) {
|
||||
assert(!forkable_stack_empty(s));
|
||||
forkable_stack_check(s);
|
||||
return (void*)(s->stk + s->pos);
|
||||
}
|
||||
|
||||
static void* forkable_stack_peek_next(struct forkable_stack* s, void* top) {
|
||||
forkable_stack_check(s);
|
||||
struct forkable_stack_header* elem = top;
|
||||
int elempos = (char*)top - s->stk;
|
||||
assert(elempos >= 0 && elempos < s->length);
|
||||
assert(elempos + elem->next_delta <= s->length);
|
||||
if (elempos + elem->next_delta < s->length) {
|
||||
return (void*)(s->stk + elempos + elem->next_delta);
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Returns 1 if the next forkable_stack_pop will permanently remove an
|
||||
// object from the stack (i.e. the top object was not saved with a fork)
|
||||
static int forkable_stack_pop_will_free(struct forkable_stack* s) {
|
||||
return s->pos < s->savedlimit ? 1 : 0;
|
||||
}
|
||||
|
||||
static void forkable_stack_pop(struct forkable_stack* s) {
|
||||
forkable_stack_check(s);
|
||||
struct forkable_stack_header* elem = forkable_stack_peek(s);
|
||||
s->pos += elem->next_delta;
|
||||
}
|
||||
|
||||
|
||||
|
||||
struct forkable_stack_state {
|
||||
// We save the previous pos, savedlimit as
|
||||
// length-pos, length-savedlimit since these
|
||||
// values are stable across stack reallocations.
|
||||
int prev_pos_delta, prev_limit_delta;
|
||||
};
|
||||
|
||||
static void forkable_stack_save(struct forkable_stack* s, struct forkable_stack_state* state) {
|
||||
forkable_stack_check(s);
|
||||
state->prev_pos_delta = s->length - s->pos;
|
||||
state->prev_limit_delta = s->length - s->savedlimit;
|
||||
if (s->pos < s->savedlimit) s->savedlimit = s->pos;
|
||||
}
|
||||
|
||||
static void forkable_stack_switch(struct forkable_stack* s, struct forkable_stack_state* state) {
|
||||
forkable_stack_check(s);
|
||||
int curr_pos = s->pos;
|
||||
s->pos = s->length - state->prev_pos_delta;
|
||||
state->prev_pos_delta = s->length - curr_pos;
|
||||
|
||||
int curr_limit = s->savedlimit;
|
||||
if (curr_pos < curr_limit) s->savedlimit = curr_pos;
|
||||
//state->prevlimit = curr_limit; FIXME clean up
|
||||
forkable_stack_check(s);
|
||||
}
|
||||
|
||||
static void forkable_stack_restore(struct forkable_stack* s, struct forkable_stack_state* state) {
|
||||
forkable_stack_check(s);
|
||||
assert(s->savedlimit <= s->length - state->prev_pos_delta);
|
||||
assert(s->savedlimit <= s->length - state->prev_limit_delta);
|
||||
s->pos = s->length - state->prev_pos_delta;
|
||||
s->savedlimit = s->length - state->prev_limit_delta;
|
||||
forkable_stack_check(s);
|
||||
}
|
||||
|
||||
typedef int stack_idx;
|
||||
|
||||
static stack_idx forkable_stack_to_idx(struct forkable_stack* s, void* ptr) {
|
||||
char* item = ptr;
|
||||
int pos = item - s->stk;
|
||||
assert(pos >= 0 && pos < s->length);
|
||||
return s->length - pos;
|
||||
}
|
||||
|
||||
static void* forkable_stack_from_idx(struct forkable_stack* s, stack_idx idx) {
|
||||
assert(idx >= 1 && idx <= s->length);
|
||||
return &s->stk[s->length - idx];
|
||||
}
|
||||
|
||||
#endif
|
||||
+118
@@ -0,0 +1,118 @@
|
||||
#ifndef FRAME_LAYOUT_H
|
||||
#include "forkable_stack.h"
|
||||
#include "bytecode.h"
|
||||
|
||||
struct closure {
|
||||
struct bytecode* bc;
|
||||
stack_idx env;
|
||||
};
|
||||
|
||||
struct continuation {
|
||||
struct bytecode* bc;
|
||||
stack_idx env;
|
||||
uint16_t* retaddr;
|
||||
// FIXME: probably not necessary as a separate field
|
||||
int is_backtrack_frame;
|
||||
};
|
||||
|
||||
typedef union frame_elem {
|
||||
FORKABLE_STACK_HEADER;
|
||||
struct continuation cont;
|
||||
struct closure closure;
|
||||
jv jsonval;
|
||||
} *frame_ptr;
|
||||
|
||||
/*
|
||||
* Frame layout
|
||||
* fr[0] - FORKABLE_STACK_HEADER (next pointer)
|
||||
* fr[1] - self (used to store return addresses, etc)
|
||||
* fr[2...nclosures+2] - closure params
|
||||
* fr[nclosures+2..nclosures+nlocals+2] - local variables
|
||||
*/
|
||||
|
||||
static int frame_size(struct bytecode* bc) {
|
||||
return sizeof(union frame_elem) * (bc->nclosures + bc->nlocals + 2);
|
||||
}
|
||||
|
||||
static struct continuation* frame_self(frame_ptr fr) {
|
||||
return &fr[1].cont;
|
||||
}
|
||||
|
||||
static struct closure* frame_closure_arg(frame_ptr fr, int closure) {
|
||||
assert(closure >= 0);
|
||||
assert(closure < frame_self(fr)->bc->nclosures);
|
||||
return &fr[2+closure].closure;
|
||||
}
|
||||
|
||||
static jv* frame_local_var(frame_ptr fr, int var) {
|
||||
assert(var >= 0);
|
||||
assert(var < frame_self(fr)->bc->nlocals);
|
||||
return &fr[2 + frame_self(fr)->bc->nclosures + var].jsonval;
|
||||
}
|
||||
|
||||
|
||||
static frame_ptr frame_current(struct forkable_stack* stk) {
|
||||
frame_ptr fp = forkable_stack_peek(stk);
|
||||
if (forkable_stack_peek_next(stk, fp)) {
|
||||
struct bytecode* bc = frame_self(forkable_stack_peek_next(stk, fp))->bc;
|
||||
assert(frame_self(fp)->retaddr >= bc->code && frame_self(fp)->retaddr < bc->code + bc->codelen);
|
||||
} else {
|
||||
assert(frame_self(fp)->retaddr == 0);
|
||||
}
|
||||
return fp;
|
||||
}
|
||||
|
||||
static struct bytecode* frame_current_bytecode(struct forkable_stack* stk) {
|
||||
return frame_self(frame_current(stk))->bc;
|
||||
}
|
||||
static uint16_t** frame_current_retaddr(struct forkable_stack* stk) {
|
||||
return &frame_self(frame_current(stk))->retaddr;
|
||||
}
|
||||
|
||||
static frame_ptr frame_get_parent(struct forkable_stack* stk, frame_ptr fr) {
|
||||
return forkable_stack_from_idx(stk, frame_self(fr)->env);
|
||||
}
|
||||
|
||||
static frame_ptr frame_get_level(struct forkable_stack* stk, frame_ptr fr, int level) {
|
||||
for (int i=0; i<level; i++) {
|
||||
fr = frame_get_parent(stk, fr);
|
||||
}
|
||||
return fr;
|
||||
}
|
||||
|
||||
static frame_ptr frame_push(struct forkable_stack* stk, struct closure cl, uint16_t* retaddr) {
|
||||
frame_ptr fp = forkable_stack_push(stk, frame_size(cl.bc));
|
||||
struct continuation* cc = frame_self(fp);
|
||||
cc->bc = cl.bc;
|
||||
cc->env = cl.env;
|
||||
cc->retaddr = retaddr;
|
||||
cc->is_backtrack_frame = 0;
|
||||
for (int i=0; i<cl.bc->nlocals; i++) {
|
||||
*frame_local_var(fp, i) = jv_invalid();
|
||||
}
|
||||
return fp;
|
||||
}
|
||||
|
||||
static frame_ptr frame_push_backtrack(struct forkable_stack* stk, uint16_t* retaddr) {
|
||||
struct continuation cc = *frame_self(frame_current(stk));
|
||||
frame_ptr fp = forkable_stack_push(stk, sizeof(union frame_elem) * 2);
|
||||
assert(!cc.is_backtrack_frame);
|
||||
cc.is_backtrack_frame = 1;
|
||||
cc.retaddr = retaddr;
|
||||
*frame_self(fp) = cc;
|
||||
return fp;
|
||||
}
|
||||
|
||||
static void frame_pop(struct forkable_stack* stk) {
|
||||
frame_ptr fp = frame_current(stk);
|
||||
if (forkable_stack_pop_will_free(stk) &&
|
||||
!frame_self(fp)->is_backtrack_frame) {
|
||||
int nlocals = frame_self(fp)->bc->nlocals;
|
||||
for (int i=0; i<nlocals; i++) {
|
||||
jv_free(*frame_local_var(fp, i));
|
||||
}
|
||||
}
|
||||
forkable_stack_pop(stk);
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,30 @@
|
||||
#!/usr/bin/python
|
||||
|
||||
mask = lambda n: (1 << n) - 1
|
||||
|
||||
|
||||
def print_table(type, name, t):
|
||||
assert len(t) == 256
|
||||
print("static const %s %s[] =" % (type, name))
|
||||
first = True
|
||||
for i in range(0,len(t),16):
|
||||
print ((" {" if i == 0 else " ") +
|
||||
", ".join("0x%02x"%n for n in t[i:i+16]) +
|
||||
("," if i + 16 < 256 else "};"))
|
||||
|
||||
|
||||
def utf8info(c):
|
||||
if c < 0x80: return 1, mask(7)
|
||||
if 0x80 <= c <= 0xBF: return 255, mask(6)
|
||||
if 0xC0 <= c <= 0xC1: return 0, 0
|
||||
if 0xC2 <= c <= 0xDF: return 2, mask(5)
|
||||
if 0xE0 <= c <= 0xEF: return 3, mask(4)
|
||||
if 0xF0 <= c <= 0xF4: return 4, mask(3)
|
||||
if 0xF4 <= c <= 0xFF: return 0, 0
|
||||
|
||||
table = lambda i: [utf8info(c)[i] for c in range(256)]
|
||||
|
||||
print("#define UTF8_CONTINUATION_BYTE ((unsigned char)255)")
|
||||
|
||||
print_table("unsigned char", "utf8_coding_length", table(0))
|
||||
print_table("unsigned char", "utf8_coding_bits", table(1))
|
||||
@@ -0,0 +1,246 @@
|
||||
#include <assert.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include "jv.h"
|
||||
#include "execute.h"
|
||||
|
||||
static void jv_test();
|
||||
static void run_jq_tests();
|
||||
|
||||
FILE* testdata;
|
||||
|
||||
int main(int argc, char* argv[]) {
|
||||
jv_test();
|
||||
if (argc == 1) {
|
||||
testdata = fopen("testdata", "r");
|
||||
} else if (argc == 2) {
|
||||
if (!strcmp(argv[1], "-")) {
|
||||
testdata = stdin;
|
||||
} else {
|
||||
testdata = fopen(argv[1], "r");
|
||||
}
|
||||
} else {
|
||||
printf("usage: %s OR cat testdata | %s - OR %s testdata\n", argv[0], argv[0], argv[0]);
|
||||
return 127;
|
||||
}
|
||||
run_jq_tests();
|
||||
if (testdata != stdin) fclose(testdata);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
static int skipline(const char* buf) {
|
||||
int p = 0;
|
||||
while (buf[p] == ' ' || buf[p] == '\t') p++;
|
||||
if (buf[p] == '#' || buf[p] == '\n' || buf[p] == 0) return 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void run_jq_tests() {
|
||||
char buf[4096];
|
||||
int tests = 0, passed = 0, invalid = 0;
|
||||
|
||||
while (1) {
|
||||
if (!fgets(buf, sizeof(buf), testdata)) break;
|
||||
if (skipline(buf)) continue;
|
||||
if (buf[strlen(buf)-1] == '\n') buf[strlen(buf)-1] = 0;
|
||||
printf("Testing %s\n", buf);
|
||||
int pass = 1;
|
||||
tests++;
|
||||
struct bytecode* bc = jq_compile(buf);
|
||||
if (!bc) {invalid++; continue;}
|
||||
#if JQ_DEBUG
|
||||
printf("Disassembly:\n");
|
||||
dump_disassembly(2, bc);
|
||||
printf("\n");
|
||||
#endif
|
||||
fgets(buf, sizeof(buf), testdata);
|
||||
jv input = jv_parse(buf);
|
||||
if (!jv_is_valid(input)){ invalid++; continue; }
|
||||
jq_init(bc, input);
|
||||
|
||||
while (fgets(buf, sizeof(buf), testdata)) {
|
||||
if (skipline(buf)) break;
|
||||
jv expected = jv_parse(buf);
|
||||
if (!jv_is_valid(expected)){ invalid++; continue; }
|
||||
jv actual = jq_next();
|
||||
if (!jv_is_valid(actual)) {
|
||||
jv_free(actual);
|
||||
printf("*** Insufficient results\n");
|
||||
pass = 0;
|
||||
break;
|
||||
} else if (!jv_equal(jv_copy(expected), jv_copy(actual))) {
|
||||
printf("*** Expected ");
|
||||
jv_dump(jv_copy(expected), 0);
|
||||
printf(", but got ");
|
||||
jv_dump(jv_copy(actual), 0);
|
||||
printf("\n");
|
||||
pass = 0;
|
||||
}
|
||||
jv as_string = jv_dump_string(jv_copy(expected), rand() & ~JV_PRINT_COLOUR);
|
||||
jv reparsed = jv_parse_sized(jv_string_value(as_string), jv_string_length(jv_copy(as_string)));
|
||||
assert(jv_equal(jv_copy(expected), jv_copy(reparsed)));
|
||||
jv_free(as_string);
|
||||
jv_free(reparsed);
|
||||
jv_free(expected);
|
||||
jv_free(actual);
|
||||
}
|
||||
if (pass) {
|
||||
jv extra = jq_next();
|
||||
if (jv_is_valid(extra)) {
|
||||
printf("*** Superfluous result: ");
|
||||
jv_dump(extra, 0);
|
||||
printf("\n");
|
||||
pass = 0;
|
||||
} else {
|
||||
jv_free(extra);
|
||||
}
|
||||
}
|
||||
jq_teardown();
|
||||
bytecode_free(bc);
|
||||
passed+=pass;
|
||||
}
|
||||
printf("%d of %d tests passed (%d malformed)\n", passed,tests,invalid);
|
||||
if (passed != tests) exit(1);
|
||||
}
|
||||
|
||||
|
||||
static void jv_test() {
|
||||
/// Arrays and numbers
|
||||
{
|
||||
jv a = jv_array();
|
||||
assert(jv_get_kind(a) == JV_KIND_ARRAY);
|
||||
assert(jv_array_length(jv_copy(a)) == 0);
|
||||
assert(jv_array_length(jv_copy(a)) == 0);
|
||||
|
||||
a = jv_array_append(a, jv_number(42));
|
||||
assert(jv_array_length(jv_copy(a)) == 1);
|
||||
assert(jv_number_value(jv_array_get(jv_copy(a), 0)) == 42);
|
||||
|
||||
jv a2 = jv_array_append(jv_array(), jv_number(42));
|
||||
assert(jv_equal(jv_copy(a), jv_copy(a)));
|
||||
assert(jv_equal(jv_copy(a2), jv_copy(a2)));
|
||||
assert(jv_equal(jv_copy(a), jv_copy(a2)));
|
||||
assert(jv_equal(jv_copy(a2), jv_copy(a)));
|
||||
jv_free(a2);
|
||||
|
||||
a2 = jv_array_append(jv_array(), jv_number(19));
|
||||
assert(!jv_equal(jv_copy(a), jv_copy(a2)));
|
||||
assert(!jv_equal(jv_copy(a2), jv_copy(a)));
|
||||
jv_free(a2);
|
||||
|
||||
|
||||
assert(a.val.complex.ptr->count == 1);
|
||||
a = jv_array_append(a, jv_copy(a));
|
||||
assert(a.val.complex.ptr->count == 1);
|
||||
|
||||
assert(jv_array_length(jv_copy(a)) == 2);
|
||||
assert(jv_number_value(jv_array_get(jv_copy(a), 0)) == 42);
|
||||
|
||||
for (int i=0; i<10; i++) {
|
||||
jv subarray = jv_array_get(jv_copy(a), 1);
|
||||
assert(jv_get_kind(subarray) == JV_KIND_ARRAY);
|
||||
assert(jv_array_length(jv_copy(subarray)) == 1);
|
||||
assert(jv_number_value(jv_array_get(jv_copy(subarray), 0)) == 42);
|
||||
jv_free(subarray);
|
||||
}
|
||||
|
||||
|
||||
jv subarray = jv_array_get(jv_copy(a), 1);
|
||||
assert(jv_get_kind(subarray) == JV_KIND_ARRAY);
|
||||
assert(jv_array_length(jv_copy(subarray)) == 1);
|
||||
assert(jv_number_value(jv_array_get(jv_copy(subarray), 0)) == 42);
|
||||
|
||||
jv sub2 = jv_copy(subarray);
|
||||
sub2 = jv_array_append(sub2, jv_number(19));
|
||||
|
||||
assert(jv_get_kind(sub2) == JV_KIND_ARRAY);
|
||||
assert(jv_array_length(jv_copy(sub2)) == 2);
|
||||
assert(jv_number_value(jv_array_get(jv_copy(sub2), 0)) == 42);
|
||||
assert(jv_number_value(jv_array_get(jv_copy(sub2), 1)) == 19);
|
||||
|
||||
assert(jv_get_kind(subarray) == JV_KIND_ARRAY);
|
||||
assert(jv_array_length(jv_copy(subarray)) == 1);
|
||||
assert(jv_number_value(jv_array_get(jv_copy(subarray), 0)) == 42);
|
||||
|
||||
jv_free(subarray);
|
||||
|
||||
void* before = sub2.val.complex.ptr;
|
||||
sub2 = jv_array_append(sub2, jv_number(200));
|
||||
void* after = sub2.val.complex.ptr;
|
||||
assert(before == after);
|
||||
jv_free(sub2);
|
||||
|
||||
jv a3 = jv_array_append(jv_copy(a), jv_number(19));
|
||||
assert(jv_array_length(jv_copy(a3)) == 3);
|
||||
assert(jv_number_value(jv_array_get(jv_copy(a3), 0)) == 42);
|
||||
assert(jv_array_length(jv_array_get(jv_copy(a3), 1)) == 1);
|
||||
assert(jv_number_value(jv_array_get(jv_copy(a3), 2)) == 19);
|
||||
jv_free(a3);
|
||||
|
||||
|
||||
assert(jv_array_length(jv_copy(a)) == 2);
|
||||
assert(jv_number_value(jv_array_get(jv_copy(a), 0)) == 42);
|
||||
assert(jv_array_length(jv_array_get(jv_copy(a), 1)) == 1);
|
||||
|
||||
|
||||
jv_dump(jv_copy(a), 0); printf("\n");
|
||||
jv_free(a);
|
||||
}
|
||||
|
||||
|
||||
/// Strings
|
||||
{
|
||||
assert(jv_equal(jv_string("foo"), jv_string_sized("foo", 3)));
|
||||
char nasty[] = "foo\0";
|
||||
jv shortstr = jv_string(nasty), longstr = jv_string_sized(nasty, sizeof(nasty));
|
||||
assert(jv_string_length(shortstr) == (int)strlen(nasty));
|
||||
assert(jv_string_length(longstr) == (int)sizeof(nasty));
|
||||
|
||||
|
||||
char a1s[] = "hello", a2s[] = "hello", bs[] = "goodbye";
|
||||
jv a1 = jv_string(a1s), a2 = jv_string(a2s), b = jv_string(bs);
|
||||
assert(jv_equal(jv_copy(a1), jv_copy(a2)));
|
||||
assert(jv_equal(jv_copy(a2), jv_copy(a1)));
|
||||
assert(!jv_equal(jv_copy(a1), jv_copy(b)));
|
||||
|
||||
assert(jv_string_hash(jv_copy(a1)) == jv_string_hash(jv_copy(a1)));
|
||||
assert(jv_string_hash(jv_copy(a1)) == jv_string_hash(jv_copy(a2)));
|
||||
assert(jv_string_hash(jv_copy(b)) != jv_string_hash(jv_copy(a1)));
|
||||
jv_free(a1);
|
||||
jv_free(a2);
|
||||
jv_free(b);
|
||||
|
||||
assert(jv_equal(jv_string("hello42!"), jv_string_fmt("hello%d%s", 42, "!")));
|
||||
char big[20000];
|
||||
for (int i=0; i<(int)sizeof(big); i++) big[i] = 'a';
|
||||
big[sizeof(big)-1] = 0;
|
||||
jv str = jv_string_fmt("%s", big);
|
||||
assert(jv_string_length(jv_copy(str)) == sizeof(big) - 1);
|
||||
assert(!strcmp(big, jv_string_value(str)));
|
||||
jv_free(str);
|
||||
}
|
||||
|
||||
/// Objects
|
||||
{
|
||||
jv o1 = jv_object();
|
||||
o1 = jv_object_set(o1, jv_string("foo"), jv_number(42));
|
||||
o1 = jv_object_set(o1, jv_string("bar"), jv_number(24));
|
||||
assert(jv_number_value(jv_object_get(jv_copy(o1), jv_string("foo"))) == 42);
|
||||
assert(jv_number_value(jv_object_get(jv_copy(o1), jv_string("bar"))) == 24);
|
||||
|
||||
jv o2 = jv_object_set(jv_copy(o1), jv_string("foo"), jv_number(420));
|
||||
o2 = jv_object_set(o2, jv_string("bar"), jv_number(240));
|
||||
assert(jv_number_value(jv_object_get(jv_copy(o1), jv_string("foo"))) == 42);
|
||||
assert(jv_number_value(jv_object_get(jv_copy(o1), jv_string("bar"))) == 24);
|
||||
assert(jv_number_value(jv_object_get(jv_copy(o2), jv_string("foo"))) == 420);
|
||||
jv_free(o1);
|
||||
assert(jv_number_value(jv_object_get(jv_copy(o2), jv_string("bar"))) == 240);
|
||||
|
||||
jv_dump(jv_copy(o2), 0); printf("\n");
|
||||
|
||||
jv_free(o2);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,146 @@
|
||||
#ifndef JV_H
|
||||
#define JV_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include <assert.h>
|
||||
#include <stddef.h>
|
||||
|
||||
|
||||
|
||||
typedef enum {
|
||||
JV_KIND_INVALID,
|
||||
JV_KIND_NULL,
|
||||
JV_KIND_FALSE,
|
||||
JV_KIND_TRUE,
|
||||
JV_KIND_NUMBER,
|
||||
JV_KIND_STRING,
|
||||
JV_KIND_ARRAY,
|
||||
JV_KIND_OBJECT
|
||||
} jv_kind;
|
||||
|
||||
typedef struct {
|
||||
size_t count;
|
||||
} jv_refcnt;
|
||||
|
||||
typedef struct{
|
||||
jv_refcnt* ptr;
|
||||
int i[2];
|
||||
} jv_complex;
|
||||
|
||||
typedef struct {
|
||||
jv_kind kind;
|
||||
union {
|
||||
double number;
|
||||
jv_complex complex;
|
||||
} val;
|
||||
} jv;
|
||||
|
||||
/*
|
||||
* All jv_* functions consume (decref) input and produce (incref) output
|
||||
* Except jv_copy
|
||||
*/
|
||||
|
||||
jv_kind jv_get_kind(jv);
|
||||
const char* jv_kind_name(jv_kind);
|
||||
static int jv_is_valid(jv x) { return jv_get_kind(x) != JV_KIND_INVALID; }
|
||||
|
||||
jv jv_copy(jv);
|
||||
void jv_free(jv);
|
||||
|
||||
int jv_equal(jv, jv);
|
||||
int jv_contains(jv, jv);
|
||||
|
||||
jv jv_invalid();
|
||||
jv jv_invalid_with_msg(jv);
|
||||
jv jv_invalid_get_msg(jv);
|
||||
int jv_invalid_has_msg(jv);
|
||||
|
||||
|
||||
jv jv_null();
|
||||
jv jv_true();
|
||||
jv jv_false();
|
||||
jv jv_bool(int);
|
||||
|
||||
jv jv_number(double);
|
||||
double jv_number_value(jv);
|
||||
|
||||
jv jv_array();
|
||||
jv jv_array_sized(int);
|
||||
int jv_array_length(jv);
|
||||
jv jv_array_get(jv, int);
|
||||
jv jv_array_set(jv, int, jv);
|
||||
jv jv_array_append(jv, jv);
|
||||
jv jv_array_concat(jv, jv);
|
||||
jv jv_array_slice(jv, int, int);
|
||||
|
||||
|
||||
jv jv_string(const char*);
|
||||
jv jv_string_sized(const char*, int);
|
||||
int jv_string_length(jv);
|
||||
uint32_t jv_string_hash(jv);
|
||||
const char* jv_string_value(jv);
|
||||
jv jv_string_concat(jv, jv);
|
||||
jv jv_string_fmt(const char*, ...);
|
||||
jv jv_string_append_buf(jv a, const char* buf, int len);
|
||||
jv jv_string_append_str(jv a, const char* str);
|
||||
|
||||
jv jv_object();
|
||||
jv jv_object_get(jv object, jv key);
|
||||
jv jv_object_set(jv object, jv key, jv value);
|
||||
jv jv_object_delete(jv object, jv key);
|
||||
int jv_object_length(jv object);
|
||||
jv jv_object_merge(jv, jv);
|
||||
|
||||
int jv_object_iter(jv);
|
||||
int jv_object_iter_next(jv, int);
|
||||
int jv_object_iter_valid(jv, int);
|
||||
jv jv_object_iter_key(jv, int);
|
||||
jv jv_object_iter_value(jv, int);
|
||||
#define jv_object_foreach(i,t) \
|
||||
for (int i = jv_object_iter(t); \
|
||||
jv_object_iter_valid(t, i); \
|
||||
i = jv_object_iter_next(t, i)) \
|
||||
|
||||
|
||||
|
||||
int jv_get_refcnt(jv);
|
||||
|
||||
enum { JV_PRINT_PRETTY = 1, JV_PRINT_ASCII = 2, JV_PRINT_COLOUR = 4 };
|
||||
void jv_dump(jv, int flags);
|
||||
jv jv_dump_string(jv, int flags);
|
||||
|
||||
jv jv_parse(const char* string);
|
||||
jv jv_parse_sized(const char* string, int length);
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
/*
|
||||
|
||||
true/false/null:
|
||||
check kind
|
||||
|
||||
number:
|
||||
introduce/eliminate jv
|
||||
to integer
|
||||
|
||||
array:
|
||||
copy
|
||||
free
|
||||
slice
|
||||
index
|
||||
update
|
||||
|
||||
updateslice?
|
||||
|
||||
|
||||
*/
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include "jv_alloc.h"
|
||||
|
||||
static void memory_exhausted() {
|
||||
fprintf(stderr, "error: cannot allocate memory\n");
|
||||
abort();
|
||||
}
|
||||
|
||||
void* jv_mem_alloc(size_t sz) {
|
||||
void* p = malloc(sz);
|
||||
if (!p) {
|
||||
memory_exhausted();
|
||||
}
|
||||
return p;
|
||||
}
|
||||
|
||||
void jv_mem_free(void* p) {
|
||||
free(p);
|
||||
}
|
||||
|
||||
void* jv_mem_realloc(void* p, size_t sz) {
|
||||
p = realloc(p, sz);
|
||||
if (!p) {
|
||||
memory_exhausted();
|
||||
}
|
||||
return p;
|
||||
}
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
#ifndef JV_ALLOC_H
|
||||
#define JV_ALLOC_H
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
void* jv_mem_alloc(size_t);
|
||||
void jv_mem_free(void*);
|
||||
__attribute__((warn_unused_result)) void* jv_mem_realloc(void*, size_t);
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,253 @@
|
||||
#include "jv_aux.h"
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include "jv_alloc.h"
|
||||
|
||||
jv jv_lookup(jv t, jv k) {
|
||||
jv v;
|
||||
if (jv_get_kind(t) == JV_KIND_OBJECT && jv_get_kind(k) == JV_KIND_STRING) {
|
||||
v = jv_object_get(t, k);
|
||||
if (!jv_is_valid(v)) {
|
||||
jv_free(v);
|
||||
v = jv_null();
|
||||
}
|
||||
} else if (jv_get_kind(t) == JV_KIND_ARRAY && jv_get_kind(k) == JV_KIND_NUMBER) {
|
||||
// FIXME: don't do lookup for noninteger index
|
||||
v = jv_array_get(t, (int)jv_number_value(k));
|
||||
if (!jv_is_valid(v)) {
|
||||
jv_free(v);
|
||||
v = jv_null();
|
||||
}
|
||||
} else if (jv_get_kind(t) == JV_KIND_NULL &&
|
||||
(jv_get_kind(k) == JV_KIND_STRING || jv_get_kind(k) == JV_KIND_NUMBER)) {
|
||||
jv_free(t);
|
||||
jv_free(k);
|
||||
v = jv_null();
|
||||
} else {
|
||||
v = jv_invalid_with_msg(jv_string_fmt("Cannot index %s with %s",
|
||||
jv_kind_name(jv_get_kind(t)),
|
||||
jv_kind_name(jv_get_kind(k))));
|
||||
jv_free(t);
|
||||
jv_free(k);
|
||||
}
|
||||
return v;
|
||||
}
|
||||
|
||||
jv jv_modify(jv t, jv k, jv v) {
|
||||
int isnull = jv_get_kind(t) == JV_KIND_NULL;
|
||||
if (jv_get_kind(k) == JV_KIND_STRING &&
|
||||
(jv_get_kind(t) == JV_KIND_OBJECT || isnull)) {
|
||||
if (isnull) t = jv_object();
|
||||
t = jv_object_set(t, k, v);
|
||||
} else if (jv_get_kind(k) == JV_KIND_NUMBER &&
|
||||
(jv_get_kind(t) == JV_KIND_ARRAY || isnull)) {
|
||||
if (isnull) t = jv_array();
|
||||
t = jv_array_set(t, (int)jv_number_value(k), v);
|
||||
} else {
|
||||
jv err = jv_invalid_with_msg(jv_string_fmt("Cannot update field at %s index of %s",
|
||||
jv_kind_name(jv_get_kind(t)),
|
||||
jv_kind_name(jv_get_kind(v))));
|
||||
jv_free(t);
|
||||
jv_free(k);
|
||||
jv_free(v);
|
||||
t = err;
|
||||
}
|
||||
return t;
|
||||
}
|
||||
|
||||
jv jv_insert(jv root, jv value, jv* path, int pathlen) {
|
||||
if (pathlen == 0) {
|
||||
jv_free(root);
|
||||
return value;
|
||||
}
|
||||
return jv_modify(root, jv_copy(*path),
|
||||
jv_insert(jv_lookup(jv_copy(root), jv_copy(*path)), value, path+1, pathlen-1));
|
||||
}
|
||||
|
||||
|
||||
static int string_cmp(const void* pa, const void* pb){
|
||||
const jv* a = pa;
|
||||
const jv* b = pb;
|
||||
int lena = jv_string_length(jv_copy(*a));
|
||||
int lenb = jv_string_length(jv_copy(*b));
|
||||
int minlen = lena < lenb ? lena : lenb;
|
||||
int r = memcmp(jv_string_value(*a), jv_string_value(*b), minlen);
|
||||
if (r == 0) r = lena - lenb;
|
||||
return r;
|
||||
}
|
||||
|
||||
jv jv_keys(jv x) {
|
||||
if (jv_get_kind(x) == JV_KIND_OBJECT) {
|
||||
int nkeys = jv_object_length(jv_copy(x));
|
||||
jv* keys = jv_mem_alloc(sizeof(jv) * nkeys);
|
||||
int kidx = 0;
|
||||
jv_object_foreach(i, x) {
|
||||
keys[kidx++] = jv_object_iter_key(x, i);
|
||||
}
|
||||
qsort(keys, nkeys, sizeof(jv), string_cmp);
|
||||
jv answer = jv_array_sized(nkeys);
|
||||
for (int i = 0; i<nkeys; i++) {
|
||||
answer = jv_array_append(answer, keys[i]);
|
||||
}
|
||||
jv_mem_free(keys);
|
||||
jv_free(x);
|
||||
return answer;
|
||||
} else if (jv_get_kind(x) == JV_KIND_ARRAY) {
|
||||
int n = jv_array_length(x);
|
||||
jv answer = jv_array();
|
||||
for (int i=0; i<n; i++){
|
||||
answer = jv_array_set(answer, i, jv_number(i));
|
||||
}
|
||||
return answer;
|
||||
} else {
|
||||
assert(0 && "jv_keys passed something neither object nor array");
|
||||
return jv_invalid();
|
||||
}
|
||||
}
|
||||
|
||||
int jv_cmp(jv a, jv b) {
|
||||
if (jv_get_kind(a) != jv_get_kind(b)) {
|
||||
int r = (int)jv_get_kind(a) - (int)jv_get_kind(b);
|
||||
jv_free(a);
|
||||
jv_free(b);
|
||||
return r;
|
||||
}
|
||||
int r = 0;
|
||||
switch (jv_get_kind(a)) {
|
||||
default:
|
||||
assert(0 && "invalid kind passed to jv_cmp");
|
||||
case JV_KIND_NULL:
|
||||
case JV_KIND_FALSE:
|
||||
case JV_KIND_TRUE:
|
||||
// there's only one of each of these values
|
||||
r = 0;
|
||||
break;
|
||||
|
||||
case JV_KIND_NUMBER: {
|
||||
double da = jv_number_value(a), db = jv_number_value(b);
|
||||
|
||||
// handle NaN as though it were null
|
||||
if (da != da) r = jv_cmp(jv_null(), jv_number(db));
|
||||
else if (db != db) r = jv_cmp(jv_number(da), jv_null());
|
||||
else if (da < db) r = -1;
|
||||
else if (da == db) r = 0;
|
||||
else r = 1;
|
||||
break;
|
||||
}
|
||||
|
||||
case JV_KIND_STRING: {
|
||||
r = string_cmp(&a, &b);
|
||||
break;
|
||||
}
|
||||
|
||||
case JV_KIND_ARRAY: {
|
||||
// Lexical ordering of arrays
|
||||
int i = 0;
|
||||
while (r == 0) {
|
||||
int a_done = i >= jv_array_length(jv_copy(a));
|
||||
int b_done = i >= jv_array_length(jv_copy(b));
|
||||
if (a_done || b_done) {
|
||||
r = b_done - a_done; //suddenly, logic
|
||||
break;
|
||||
}
|
||||
jv xa = jv_array_get(jv_copy(a), i);
|
||||
jv xb = jv_array_get(jv_copy(b), i);
|
||||
r = jv_cmp(xa, xb);
|
||||
i++;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case JV_KIND_OBJECT: {
|
||||
jv keys_a = jv_keys(jv_copy(a));
|
||||
jv keys_b = jv_keys(jv_copy(b));
|
||||
r = jv_cmp(jv_copy(keys_a), keys_b);
|
||||
if (r == 0) {
|
||||
for (int i=0; i<jv_array_length(jv_copy(keys_a)); i++) {
|
||||
jv key = jv_array_get(jv_copy(keys_a), i);
|
||||
jv xa = jv_object_get(jv_copy(a), jv_copy(key));
|
||||
jv xb = jv_object_get(jv_copy(b), key);
|
||||
r = jv_cmp(xa, xb);
|
||||
if (r) break;
|
||||
}
|
||||
}
|
||||
jv_free(keys_a);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
jv_free(a);
|
||||
jv_free(b);
|
||||
return r;
|
||||
}
|
||||
|
||||
|
||||
struct sort_entry {
|
||||
jv object;
|
||||
jv key;
|
||||
};
|
||||
|
||||
static int sort_cmp(const void* pa, const void* pb) {
|
||||
const struct sort_entry* a = pa;
|
||||
const struct sort_entry* b = pb;
|
||||
int r = jv_cmp(jv_copy(a->key), jv_copy(b->key));
|
||||
// comparing by address if r == 0 makes the sort stable
|
||||
return r ? r : (int)(a - b);
|
||||
}
|
||||
|
||||
static struct sort_entry* sort_items(jv objects, jv keys) {
|
||||
assert(jv_get_kind(objects) == JV_KIND_ARRAY);
|
||||
assert(jv_get_kind(keys) == JV_KIND_ARRAY);
|
||||
assert(jv_array_length(jv_copy(objects)) == jv_array_length(jv_copy(keys)));
|
||||
int n = jv_array_length(jv_copy(objects));
|
||||
struct sort_entry* entries = jv_mem_alloc(sizeof(struct sort_entry) * n);
|
||||
for (int i=0; i<n; i++) {
|
||||
entries[i].object = jv_array_get(jv_copy(objects), i);
|
||||
entries[i].key = jv_array_get(jv_copy(keys), i);
|
||||
}
|
||||
jv_free(objects);
|
||||
jv_free(keys);
|
||||
qsort(entries, n, sizeof(struct sort_entry), sort_cmp);
|
||||
return entries;
|
||||
}
|
||||
|
||||
jv jv_sort(jv objects, jv keys) {
|
||||
assert(jv_get_kind(objects) == JV_KIND_ARRAY);
|
||||
assert(jv_get_kind(keys) == JV_KIND_ARRAY);
|
||||
assert(jv_array_length(jv_copy(objects)) == jv_array_length(jv_copy(keys)));
|
||||
int n = jv_array_length(jv_copy(objects));
|
||||
struct sort_entry* entries = sort_items(objects, keys);
|
||||
jv ret = jv_array();
|
||||
for (int i=0; i<n; i++) {
|
||||
jv_free(entries[i].key);
|
||||
ret = jv_array_set(ret, i, entries[i].object);
|
||||
}
|
||||
jv_mem_free(entries);
|
||||
return ret;
|
||||
}
|
||||
|
||||
jv jv_group(jv objects, jv keys) {
|
||||
assert(jv_get_kind(objects) == JV_KIND_ARRAY);
|
||||
assert(jv_get_kind(keys) == JV_KIND_ARRAY);
|
||||
assert(jv_array_length(jv_copy(objects)) == jv_array_length(jv_copy(keys)));
|
||||
int n = jv_array_length(jv_copy(objects));
|
||||
struct sort_entry* entries = sort_items(objects, keys);
|
||||
jv ret = jv_array();
|
||||
jv curr_key = entries[0].key;
|
||||
jv group = jv_array_append(jv_array(), entries[0].object);
|
||||
for (int i = 1; i < n; i++) {
|
||||
if (jv_equal(jv_copy(curr_key), jv_copy(entries[i].key))) {
|
||||
jv_free(entries[i].key);
|
||||
} else {
|
||||
jv_free(curr_key);
|
||||
curr_key = entries[i].key;
|
||||
ret = jv_array_append(ret, group);
|
||||
group = jv_array();
|
||||
}
|
||||
group = jv_array_append(group, entries[i].object);
|
||||
}
|
||||
jv_free(curr_key);
|
||||
ret = jv_array_append(ret, group);
|
||||
jv_mem_free(entries);
|
||||
return ret;
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
#ifndef JV_AUX_H
|
||||
#define JV_AUX_H
|
||||
|
||||
#include "jv.h"
|
||||
|
||||
jv jv_lookup(jv t, jv k);
|
||||
jv jv_modify(jv t, jv k, jv v);
|
||||
jv jv_insert(jv root, jv value, jv* path, int pathlen);
|
||||
|
||||
jv jv_keys(jv /*object or array*/);
|
||||
int jv_cmp(jv, jv);
|
||||
|
||||
jv jv_group(jv objects, jv keys);
|
||||
jv jv_sort(jv objects, jv keys);
|
||||
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,22 @@
|
||||
#ifndef JV_DTOA_H
|
||||
#define JV_DTOA_H
|
||||
#define Kmax 7
|
||||
|
||||
struct Bigint;
|
||||
struct dtoa_context {
|
||||
struct Bigint *freelist[Kmax+1];
|
||||
struct Bigint *p5s;
|
||||
};
|
||||
|
||||
void jvp_dtoa_context_init(struct dtoa_context* ctx);
|
||||
void jvp_dtoa_context_free(struct dtoa_context* ctx);
|
||||
|
||||
double jvp_strtod(struct dtoa_context* C, const char* s, char** se);
|
||||
|
||||
|
||||
char* jvp_dtoa(struct dtoa_context* C, double dd, int mode, int ndigits, int *decpt, int *sign, char **rve);
|
||||
void jvp_freedtoa(struct dtoa_context* C, char *s);
|
||||
|
||||
#define JVP_DTOA_FMT_MAX_LEN 32
|
||||
char* jvp_dtoa_fmt(struct dtoa_context* C, register char *b, double x);
|
||||
#endif
|
||||
+439
@@ -0,0 +1,439 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include "jv.h"
|
||||
#include "jv_dtoa.h"
|
||||
#include "jv_parse.h"
|
||||
#include "jv_unicode.h"
|
||||
#include "jv_alloc.h"
|
||||
|
||||
typedef const char* presult;
|
||||
|
||||
#define TRY(x) do {presult msg__ = (x); if (msg__) return msg__; } while(0)
|
||||
#ifdef __GNUC__
|
||||
#define pfunc __attribute__((warn_unused_result)) presult
|
||||
#else
|
||||
#define pfunc presult
|
||||
#endif
|
||||
|
||||
void jv_parser_init(struct jv_parser* p) {
|
||||
p->stack = 0;
|
||||
p->stacklen = p->stackpos = 0;
|
||||
p->next = jv_invalid();
|
||||
p->tokenbuf = 0;
|
||||
p->tokenlen = p->tokenpos = 0;
|
||||
p->st = JV_PARSER_NORMAL;
|
||||
p->curr_buf = 0;
|
||||
p->curr_buf_length = p->curr_buf_pos = p->curr_buf_is_partial = 0;
|
||||
p->bom_strip_position = 0;
|
||||
jvp_dtoa_context_init(&p->dtoa);
|
||||
}
|
||||
|
||||
void jv_parser_free(struct jv_parser* p) {
|
||||
jv_free(p->next);
|
||||
for (int i=0; i<p->stackpos; i++)
|
||||
jv_free(p->stack[i]);
|
||||
jv_mem_free(p->stack);
|
||||
jv_mem_free(p->tokenbuf);
|
||||
jvp_dtoa_context_free(&p->dtoa);
|
||||
}
|
||||
|
||||
static pfunc value(struct jv_parser* p, jv val) {
|
||||
if (jv_is_valid(p->next)) return "Expected separator between values";
|
||||
jv_free(p->next);
|
||||
p->next = val;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void push(struct jv_parser* p, jv v) {
|
||||
assert(p->stackpos <= p->stacklen);
|
||||
if (p->stackpos == p->stacklen) {
|
||||
p->stacklen = p->stacklen * 2 + 10;
|
||||
p->stack = jv_mem_realloc(p->stack, p->stacklen * sizeof(jv));
|
||||
}
|
||||
assert(p->stackpos < p->stacklen);
|
||||
p->stack[p->stackpos++] = v;
|
||||
}
|
||||
|
||||
static pfunc token(struct jv_parser* p, char ch) {
|
||||
switch (ch) {
|
||||
case '[':
|
||||
if (jv_is_valid(p->next)) return "Expected separator between values";
|
||||
push(p, jv_array());
|
||||
break;
|
||||
|
||||
case '{':
|
||||
if (jv_is_valid(p->next)) return "Expected separator between values";
|
||||
push(p, jv_object());
|
||||
break;
|
||||
|
||||
case ':':
|
||||
if (!jv_is_valid(p->next))
|
||||
return "Expected string key before ':'";
|
||||
if (p->stackpos == 0 || jv_get_kind(p->stack[p->stackpos-1]) != JV_KIND_OBJECT)
|
||||
return "':' not as part of an object";
|
||||
if (jv_get_kind(p->next) != JV_KIND_STRING)
|
||||
return "Object keys must be strings";
|
||||
push(p, p->next);
|
||||
p->next = jv_invalid();
|
||||
break;
|
||||
|
||||
case ',':
|
||||
if (!jv_is_valid(p->next))
|
||||
return "Expected value before ','";
|
||||
if (p->stackpos == 0)
|
||||
return "',' not as part of an object or array";
|
||||
if (jv_get_kind(p->stack[p->stackpos-1]) == JV_KIND_ARRAY) {
|
||||
p->stack[p->stackpos-1] = jv_array_append(p->stack[p->stackpos-1], p->next);
|
||||
p->next = jv_invalid();
|
||||
} else if (jv_get_kind(p->stack[p->stackpos-1]) == JV_KIND_STRING) {
|
||||
assert(p->stackpos > 1 && jv_get_kind(p->stack[p->stackpos-2]) == JV_KIND_OBJECT);
|
||||
p->stack[p->stackpos-2] = jv_object_set(p->stack[p->stackpos-2],
|
||||
p->stack[p->stackpos-1], p->next);
|
||||
p->stackpos--;
|
||||
p->next = jv_invalid();
|
||||
} else {
|
||||
// this case hits on input like {"a", "b"}
|
||||
return "Objects must consist of key:value pairs";
|
||||
}
|
||||
break;
|
||||
|
||||
case ']':
|
||||
if (p->stackpos == 0 || jv_get_kind(p->stack[p->stackpos-1]) != JV_KIND_ARRAY)
|
||||
return "Unmatched ']'";
|
||||
if (jv_is_valid(p->next)) {
|
||||
p->stack[p->stackpos-1] = jv_array_append(p->stack[p->stackpos-1], p->next);
|
||||
p->next = jv_invalid();
|
||||
} else {
|
||||
if (jv_array_length(jv_copy(p->stack[p->stackpos-1])) != 0) {
|
||||
// this case hits on input like [1,2,3,]
|
||||
return "Expected another array element";
|
||||
}
|
||||
}
|
||||
jv_free(p->next);
|
||||
p->next = p->stack[--p->stackpos];
|
||||
break;
|
||||
|
||||
case '}':
|
||||
if (p->stackpos == 0)
|
||||
return "Unmatched '}'";
|
||||
if (jv_is_valid(p->next)) {
|
||||
if (jv_get_kind(p->stack[p->stackpos-1]) != JV_KIND_STRING)
|
||||
return "Objects must consist of key:value pairs";
|
||||
assert(p->stackpos > 1 && jv_get_kind(p->stack[p->stackpos-2]) == JV_KIND_OBJECT);
|
||||
p->stack[p->stackpos-2] = jv_object_set(p->stack[p->stackpos-2],
|
||||
p->stack[p->stackpos-1], p->next);
|
||||
p->stackpos--;
|
||||
p->next = jv_invalid();
|
||||
} else {
|
||||
if (jv_get_kind(p->stack[p->stackpos-1]) != JV_KIND_OBJECT)
|
||||
return "Unmatched '}'";
|
||||
if (jv_object_length(jv_copy(p->stack[p->stackpos-1])) != 0)
|
||||
return "Expected another key-value pair";
|
||||
}
|
||||
jv_free(p->next);
|
||||
p->next = p->stack[--p->stackpos];
|
||||
break;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
static void tokenadd(struct jv_parser* p, char c) {
|
||||
assert(p->tokenpos <= p->tokenlen);
|
||||
if (p->tokenpos == p->tokenlen) {
|
||||
p->tokenlen = p->tokenlen*2 + 256;
|
||||
p->tokenbuf = jv_mem_realloc(p->tokenbuf, p->tokenlen);
|
||||
}
|
||||
assert(p->tokenpos < p->tokenlen);
|
||||
p->tokenbuf[p->tokenpos++] = c;
|
||||
}
|
||||
|
||||
static int unhex4(char* hex) {
|
||||
int r = 0;
|
||||
for (int i=0; i<4; i++) {
|
||||
char c = *hex++;
|
||||
int n;
|
||||
if ('0' <= c && c <= '9') n = c - '0';
|
||||
else if ('a' <= c && c <= 'f') n = c - 'a' + 10;
|
||||
else if ('A' <= c && c <= 'F') n = c - 'A' + 10;
|
||||
else return -1;
|
||||
r <<= 4;
|
||||
r |= n;
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
static pfunc found_string(struct jv_parser* p) {
|
||||
char* in = p->tokenbuf;
|
||||
char* out = p->tokenbuf;
|
||||
char* end = p->tokenbuf + p->tokenpos;
|
||||
|
||||
while (in < end) {
|
||||
char c = *in++;
|
||||
if (c == '\\') {
|
||||
if (in >= end)
|
||||
return "Expected escape character at end of string";
|
||||
c = *in++;
|
||||
switch (c) {
|
||||
case '\\':
|
||||
case '"':
|
||||
case '/': *out++ = c; break;
|
||||
case 'b': *out++ = '\b'; break;
|
||||
case 'f': *out++ = '\f'; break;
|
||||
case 't': *out++ = '\t'; break;
|
||||
case 'n': *out++ = '\n'; break;
|
||||
case 'r': *out++ = '\r'; break;
|
||||
|
||||
case 'u':
|
||||
/* ahh, the complicated case */
|
||||
if (in + 4 > end)
|
||||
return "Invalid \\uXXXX escape";
|
||||
int hexvalue = unhex4(in);
|
||||
if (hexvalue < 0)
|
||||
return "Invalid characters in \\uXXXX escape";
|
||||
unsigned long codepoint = (unsigned long)hexvalue;
|
||||
in += 4;
|
||||
if (0xD800 <= codepoint && codepoint <= 0xDBFF) {
|
||||
/* who thought UTF-16 surrogate pairs were a good idea? */
|
||||
if (in + 6 > end || in[0] != '\\' || in[1] != 'u')
|
||||
return "Invalid \\uXXXX\\uXXXX surrogate pair escape";
|
||||
unsigned long surrogate = unhex4(in+2);
|
||||
if (!(0xDC00 <= surrogate && surrogate <= 0xDFFF))
|
||||
return "Invalid \\uXXXX\\uXXXX surrogate pair escape";
|
||||
in += 6;
|
||||
codepoint = 0x10000 + (((codepoint - 0xD800) << 10)
|
||||
|(surrogate - 0xDC00));
|
||||
}
|
||||
// FIXME assert valid codepoint
|
||||
out += jvp_utf8_encode(codepoint, out);
|
||||
break;
|
||||
|
||||
default:
|
||||
return "Invalid escape";
|
||||
}
|
||||
} else {
|
||||
*out++ = c;
|
||||
}
|
||||
}
|
||||
TRY(value(p, jv_string_sized(p->tokenbuf, out - p->tokenbuf)));
|
||||
p->tokenpos = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static pfunc check_literal(struct jv_parser* p) {
|
||||
if (p->tokenpos == 0) return 0;
|
||||
|
||||
const char* pattern = 0;
|
||||
int plen;
|
||||
jv v;
|
||||
switch (p->tokenbuf[0]) {
|
||||
case 't': pattern = "true"; plen = 4; v = jv_true(); break;
|
||||
case 'f': pattern = "false"; plen = 5; v = jv_false(); break;
|
||||
case 'n': pattern = "null"; plen = 4; v = jv_null(); break;
|
||||
}
|
||||
if (pattern) {
|
||||
if (p->tokenpos != plen) return "Invalid literal";
|
||||
for (int i=0; i<plen; i++)
|
||||
if (p->tokenbuf[i] != pattern[i])
|
||||
return "Invalid literal";
|
||||
TRY(value(p, v));
|
||||
} else {
|
||||
// FIXME: better parser
|
||||
p->tokenbuf[p->tokenpos] = 0; // FIXME: invalid
|
||||
char* end = 0;
|
||||
double d = jvp_strtod(&p->dtoa, p->tokenbuf, &end);
|
||||
if (end == 0 || *end != 0)
|
||||
return "Invalid numeric literal";
|
||||
TRY(value(p, jv_number(d)));
|
||||
}
|
||||
p->tokenpos = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
typedef enum {
|
||||
LITERAL,
|
||||
WHITESPACE,
|
||||
STRUCTURE,
|
||||
QUOTE,
|
||||
INVALID
|
||||
} chclass;
|
||||
|
||||
static chclass classify(char c) {
|
||||
switch (c) {
|
||||
case ' ':
|
||||
case '\t':
|
||||
case '\r':
|
||||
case '\n':
|
||||
return WHITESPACE;
|
||||
case '"':
|
||||
return QUOTE;
|
||||
case '[':
|
||||
case ',':
|
||||
case ']':
|
||||
case '{':
|
||||
case ':':
|
||||
case '}':
|
||||
return STRUCTURE;
|
||||
default:
|
||||
return LITERAL;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static presult OK = "output produced";
|
||||
|
||||
static int check_done(struct jv_parser* p, jv* out) {
|
||||
if (p->stackpos == 0 && jv_is_valid(p->next)) {
|
||||
*out = p->next;
|
||||
p->next = jv_invalid();
|
||||
return 1;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
static pfunc scan(struct jv_parser* p, char ch, jv* out) {
|
||||
presult answer = 0;
|
||||
if (p->st == JV_PARSER_NORMAL) {
|
||||
chclass cls = classify(ch);
|
||||
if (cls != LITERAL) {
|
||||
TRY(check_literal(p));
|
||||
if (check_done(p, out)) answer = OK;
|
||||
}
|
||||
switch (cls) {
|
||||
case LITERAL:
|
||||
tokenadd(p, ch);
|
||||
break;
|
||||
case WHITESPACE:
|
||||
break;
|
||||
case QUOTE:
|
||||
p->st = JV_PARSER_STRING;
|
||||
break;
|
||||
case STRUCTURE:
|
||||
TRY(token(p, ch));
|
||||
break;
|
||||
case INVALID:
|
||||
return "Invalid character";
|
||||
}
|
||||
if (check_done(p, out)) answer = OK;
|
||||
} else {
|
||||
if (ch == '"' && p->st == JV_PARSER_STRING) {
|
||||
TRY(found_string(p));
|
||||
p->st = JV_PARSER_NORMAL;
|
||||
if (check_done(p, out)) answer = OK;
|
||||
} else {
|
||||
tokenadd(p, ch);
|
||||
if (ch == '\\' && p->st == JV_PARSER_STRING) {
|
||||
p->st = JV_PARSER_STRING_ESCAPE;
|
||||
} else {
|
||||
p->st = JV_PARSER_STRING;
|
||||
}
|
||||
}
|
||||
}
|
||||
return answer;
|
||||
}
|
||||
|
||||
static unsigned char UTF8_BOM[] = {0xEF,0xBB,0xBF};
|
||||
|
||||
void jv_parser_set_buf(struct jv_parser* p, const char* buf, int length, int is_partial) {
|
||||
assert((p->curr_buf == 0 || p->curr_buf_pos == p->curr_buf_length)
|
||||
&& "previous buffer not exhausted");
|
||||
while (p->bom_strip_position < sizeof(UTF8_BOM)) {
|
||||
if ((unsigned char)*buf == UTF8_BOM[p->bom_strip_position]) {
|
||||
// matched a BOM character
|
||||
buf++;
|
||||
length--;
|
||||
p->bom_strip_position++;
|
||||
} else {
|
||||
if (p->bom_strip_position == 0) {
|
||||
// no BOM in this document
|
||||
p->bom_strip_position = sizeof(UTF8_BOM);
|
||||
} else {
|
||||
// malformed BOM (prefix present, rest missing)
|
||||
p->bom_strip_position = 0xff;
|
||||
}
|
||||
}
|
||||
}
|
||||
p->curr_buf = buf;
|
||||
p->curr_buf_length = length;
|
||||
p->curr_buf_pos = 0;
|
||||
p->curr_buf_is_partial = is_partial;
|
||||
}
|
||||
|
||||
jv jv_parser_next(struct jv_parser* p) {
|
||||
assert(p->curr_buf && "a buffer must be provided");
|
||||
if (p->bom_strip_position == 0xff) return jv_invalid_with_msg(jv_string("Malformed BOM"));
|
||||
jv value;
|
||||
presult msg = 0;
|
||||
while (!msg && p->curr_buf_pos < p->curr_buf_length) {
|
||||
char ch = p->curr_buf[p->curr_buf_pos++];
|
||||
msg = scan(p, ch, &value);
|
||||
}
|
||||
if (msg == OK) {
|
||||
return value;
|
||||
} else if (msg) {
|
||||
return jv_invalid_with_msg(jv_string(msg));
|
||||
} else if (p->curr_buf_is_partial) {
|
||||
assert(p->curr_buf_pos == p->curr_buf_length);
|
||||
// need another buffer
|
||||
return jv_invalid();
|
||||
} else {
|
||||
assert(p->curr_buf_pos == p->curr_buf_length);
|
||||
// at EOF
|
||||
if (p->st != JV_PARSER_NORMAL)
|
||||
return jv_invalid_with_msg(jv_string("Unfinished string"));
|
||||
if ((msg = check_literal(p)))
|
||||
return jv_invalid_with_msg(jv_string(msg));
|
||||
if (p->stackpos != 0)
|
||||
return jv_invalid_with_msg(jv_string("Unfinished JSON term"));
|
||||
// p->next is either invalid (nothing here but no syntax error)
|
||||
// or valid (this is the value). either way it's the thing to return
|
||||
value = p->next;
|
||||
p->next = jv_invalid();
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
jv jv_parse_sized(const char* string, int length) {
|
||||
struct jv_parser parser;
|
||||
jv_parser_init(&parser);
|
||||
jv_parser_set_buf(&parser, string, length, 0);
|
||||
jv value = jv_parser_next(&parser);
|
||||
if (jv_is_valid(value)) {
|
||||
jv next = jv_parser_next(&parser);
|
||||
if (jv_is_valid(next)) {
|
||||
// multiple JSON values, we only wanted one
|
||||
jv_free(value);
|
||||
jv_free(next);
|
||||
value = jv_invalid_with_msg(jv_string("Unexpected extra JSON values"));
|
||||
} else if (jv_invalid_has_msg(jv_copy(next))) {
|
||||
// parser error after the first JSON value
|
||||
jv_free(value);
|
||||
value = next;
|
||||
} else {
|
||||
// a single valid JSON value
|
||||
jv_free(next);
|
||||
}
|
||||
} else if (jv_invalid_has_msg(jv_copy(value))) {
|
||||
// parse error, we'll return it
|
||||
} else {
|
||||
// no value at all
|
||||
jv_free(value);
|
||||
value = jv_invalid_with_msg(jv_string("Expected JSON value"));
|
||||
}
|
||||
jv_parser_free(&parser);
|
||||
|
||||
if (!jv_is_valid(value) && jv_invalid_has_msg(jv_copy(value))) {
|
||||
jv msg = jv_invalid_get_msg(value);
|
||||
value = jv_invalid_with_msg(jv_string_fmt("%s (while parsing '%s')",
|
||||
jv_string_value(msg),
|
||||
string));
|
||||
jv_free(msg);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
jv jv_parse(const char* string) {
|
||||
return jv_parse_sized(string, strlen(string));
|
||||
}
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
#ifndef JV_PARSE_H
|
||||
#define JV_PARSE_H
|
||||
#include "jv_dtoa.h"
|
||||
struct jv_parser {
|
||||
const char* curr_buf;
|
||||
int curr_buf_length;
|
||||
int curr_buf_pos;
|
||||
int curr_buf_is_partial;
|
||||
unsigned bom_strip_position;
|
||||
|
||||
jv* stack;
|
||||
int stackpos;
|
||||
int stacklen;
|
||||
jv next;
|
||||
|
||||
char* tokenbuf;
|
||||
int tokenpos;
|
||||
int tokenlen;
|
||||
|
||||
struct dtoa_context dtoa;
|
||||
|
||||
enum {
|
||||
JV_PARSER_NORMAL,
|
||||
JV_PARSER_STRING,
|
||||
JV_PARSER_STRING_ESCAPE
|
||||
} st;
|
||||
};
|
||||
|
||||
void jv_parser_init(struct jv_parser* p);
|
||||
void jv_parser_free(struct jv_parser* p);
|
||||
|
||||
void jv_parser_set_buf(struct jv_parser* p, const char* buf, int length, int is_partial);
|
||||
|
||||
jv jv_parser_next(struct jv_parser* p);
|
||||
#endif
|
||||
+249
@@ -0,0 +1,249 @@
|
||||
#include "jv.h"
|
||||
#include <stdio.h>
|
||||
#include <float.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "jv_dtoa.h"
|
||||
#include "jv_unicode.h"
|
||||
|
||||
#define ESC "\033"
|
||||
#define COL(c) (ESC "[" c "m")
|
||||
#define COLRESET (ESC "[0m")
|
||||
|
||||
// Colour table. See http://en.wikipedia.org/wiki/ANSI_escape_code#Colors
|
||||
// for how to choose these.
|
||||
static jv_kind colour_kinds[] =
|
||||
{JV_KIND_NULL, JV_KIND_FALSE, JV_KIND_TRUE, JV_KIND_NUMBER,
|
||||
JV_KIND_STRING, JV_KIND_ARRAY, JV_KIND_OBJECT};
|
||||
static const char* colours[] =
|
||||
{COL("30;1"), COL("0"), COL("0"), COL("0"),
|
||||
COL("32"), COL("37"), COL("37")};
|
||||
#define FIELD_COLOUR COL("34;1")
|
||||
|
||||
static void put_buf(const char* s, int len, FILE* fout, jv* strout) {
|
||||
if (strout) {
|
||||
*strout = jv_string_append_buf(*strout, s, len);
|
||||
} else {
|
||||
fwrite(s, 1, len, fout);
|
||||
}
|
||||
}
|
||||
|
||||
static void put_char(char c, FILE* fout, jv* strout) {
|
||||
put_buf(&c, 1, fout, strout);
|
||||
}
|
||||
|
||||
static void put_str(const char* s, FILE* fout, jv* strout) {
|
||||
put_buf(s, strlen(s), fout, strout);
|
||||
}
|
||||
|
||||
static void put_space(int n, FILE* fout, jv* strout) {
|
||||
while (n--) {
|
||||
put_char(' ', fout, strout);
|
||||
}
|
||||
}
|
||||
|
||||
static void jvp_dump_string(jv str, int ascii_only, FILE* F, jv* S) {
|
||||
assert(jv_get_kind(str) == JV_KIND_STRING);
|
||||
const char* i = jv_string_value(str);
|
||||
const char* end = i + jv_string_length(jv_copy(str));
|
||||
const char* cstart;
|
||||
int c = 0;
|
||||
char buf[32];
|
||||
put_char('"', F, S);
|
||||
while ((i = jvp_utf8_next((cstart = i), end, &c))) {
|
||||
assert(c != -1);
|
||||
int unicode_escape = 0;
|
||||
if (0x20 <= c && c <= 0x7E) {
|
||||
// printable ASCII
|
||||
if (c == '"' || c == '\\') {
|
||||
put_char('\\', F, S);
|
||||
}
|
||||
put_char(c, F, S);
|
||||
} else if (c < 0x20 || c == 0x7F) {
|
||||
// ASCII control character
|
||||
switch (c) {
|
||||
case '\b':
|
||||
put_char('\\', F, S);
|
||||
put_char('b', F, S);
|
||||
break;
|
||||
case '\t':
|
||||
put_char('\\', F, S);
|
||||
put_char('t', F, S);
|
||||
break;
|
||||
case '\r':
|
||||
put_char('\\', F, S);
|
||||
put_char('r', F, S);
|
||||
break;
|
||||
case '\n':
|
||||
put_char('\\', F, S);
|
||||
put_char('n', F, S);
|
||||
break;
|
||||
case '\f':
|
||||
put_char('\\', F, S);
|
||||
put_char('f', F, S);
|
||||
break;
|
||||
default:
|
||||
unicode_escape = 1;
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
if (ascii_only) {
|
||||
unicode_escape = 1;
|
||||
} else {
|
||||
put_buf(cstart, i - cstart, F, S);
|
||||
}
|
||||
}
|
||||
if (unicode_escape) {
|
||||
if (c <= 0xffff) {
|
||||
sprintf(buf, "\\u%04x", c);
|
||||
} else {
|
||||
c -= 0x10000;
|
||||
sprintf(buf, "\\u%04x\\u%04x",
|
||||
0xD800 | ((c & 0xffc00) >> 10),
|
||||
0xDC00 | (c & 0x003ff));
|
||||
}
|
||||
put_str(buf, F, S);
|
||||
}
|
||||
}
|
||||
assert(c != -1);
|
||||
put_char('"', F, S);
|
||||
}
|
||||
|
||||
enum { INDENT = 2 };
|
||||
|
||||
static void jv_dump_term(struct dtoa_context* C, jv x, int flags, int indent, FILE* F, jv* S) {
|
||||
char buf[JVP_DTOA_FMT_MAX_LEN];
|
||||
const char* colour = 0;
|
||||
if (flags & JV_PRINT_COLOUR) {
|
||||
for (unsigned i=0; i<sizeof(colour_kinds)/sizeof(colour_kinds[0]); i++) {
|
||||
if (jv_get_kind(x) == colour_kinds[i]) {
|
||||
colour = colours[i];
|
||||
put_str(colour, F, S);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
switch (jv_get_kind(x)) {
|
||||
case JV_KIND_INVALID:
|
||||
assert(0 && "Invalid value");
|
||||
break;
|
||||
case JV_KIND_NULL:
|
||||
put_str("null", F, S);
|
||||
break;
|
||||
case JV_KIND_FALSE:
|
||||
put_str("false", F, S);
|
||||
break;
|
||||
case JV_KIND_TRUE:
|
||||
put_str("true", F, S);
|
||||
break;
|
||||
case JV_KIND_NUMBER: {
|
||||
double d = jv_number_value(x);
|
||||
if (d != d) {
|
||||
// JSON doesn't have NaN, so we'll render it as "null"
|
||||
put_str("null", F, S);
|
||||
} else {
|
||||
// Normalise infinities to something we can print in valid JSON
|
||||
if (d > DBL_MAX) d = DBL_MAX;
|
||||
if (d < -DBL_MAX) d = -DBL_MAX;
|
||||
put_str(jvp_dtoa_fmt(C, buf, d), F, S);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case JV_KIND_STRING:
|
||||
jvp_dump_string(x, flags & JV_PRINT_ASCII, F, S);
|
||||
break;
|
||||
case JV_KIND_ARRAY: {
|
||||
if (jv_array_length(jv_copy(x)) == 0) {
|
||||
put_str("[]", F, S);
|
||||
break;
|
||||
}
|
||||
put_str("[", F, S);
|
||||
if (flags & JV_PRINT_PRETTY) {
|
||||
put_char('\n', F, S);
|
||||
put_space(indent+INDENT, F, S);
|
||||
}
|
||||
for (int i=0; i<jv_array_length(jv_copy(x)); i++) {
|
||||
if (i!=0) {
|
||||
if (flags & JV_PRINT_PRETTY) {
|
||||
put_str(",\n", F, S);
|
||||
put_space(indent+INDENT, F, S);
|
||||
} else {
|
||||
put_str(",", F, S);
|
||||
}
|
||||
}
|
||||
jv_dump_term(C, jv_array_get(jv_copy(x), i), flags, indent + INDENT, F, S);
|
||||
if (colour) put_str(colour, F, S);
|
||||
}
|
||||
if (flags & JV_PRINT_PRETTY) {
|
||||
put_char('\n', F, S);
|
||||
put_space(indent, F, S);
|
||||
}
|
||||
if (colour) put_str(colour, F, S);
|
||||
put_char(']', F, S);
|
||||
break;
|
||||
}
|
||||
case JV_KIND_OBJECT: {
|
||||
if (jv_object_length(jv_copy(x)) == 0) {
|
||||
put_str("{}", F, S);
|
||||
break;
|
||||
}
|
||||
put_char('{', F, S);
|
||||
if (flags & JV_PRINT_PRETTY) {
|
||||
put_char('\n', F, S);
|
||||
put_space(indent+INDENT, F, S);
|
||||
}
|
||||
int first = 1;
|
||||
jv_object_foreach(i, x) {
|
||||
if (!first) {
|
||||
if (flags & JV_PRINT_PRETTY){
|
||||
put_str(",\n", F, S);
|
||||
put_space(indent+INDENT, F, S);
|
||||
} else {
|
||||
put_str(",", F, S);
|
||||
}
|
||||
}
|
||||
if (colour) put_str(COLRESET, F, S);
|
||||
|
||||
first = 0;
|
||||
jv key = jv_object_iter_key(x, i);
|
||||
if (colour) put_str(FIELD_COLOUR, F, S);
|
||||
jvp_dump_string(key, flags & JV_PRINT_ASCII, F, S);
|
||||
jv_free(key);
|
||||
if (colour) put_str(COLRESET, F, S);
|
||||
|
||||
if (colour) put_str(colour, F, S);
|
||||
put_str((flags & JV_PRINT_PRETTY) ? ": " : ":", F, S);
|
||||
if (colour) put_str(COLRESET, F, S);
|
||||
|
||||
jv_dump_term(C, jv_object_iter_value(x, i), flags, indent + INDENT, F, S);
|
||||
if (colour) put_str(colour, F, S);
|
||||
}
|
||||
if (flags & JV_PRINT_PRETTY) {
|
||||
put_char('\n', F, S);
|
||||
put_space(indent, F, S);
|
||||
}
|
||||
if (colour) put_str(colour, F, S);
|
||||
put_char('}', F, S);
|
||||
}
|
||||
}
|
||||
jv_free(x);
|
||||
if (colour) {
|
||||
put_str(COLRESET, F, S);
|
||||
}
|
||||
}
|
||||
|
||||
void jv_dump(jv x, int flags) {
|
||||
struct dtoa_context C;
|
||||
jvp_dtoa_context_init(&C);
|
||||
jv_dump_term(&C, x, flags, 0, stdout, 0);
|
||||
jvp_dtoa_context_free(&C);
|
||||
}
|
||||
|
||||
jv jv_dump_string(jv x, int flags) {
|
||||
struct dtoa_context C;
|
||||
jvp_dtoa_context_init(&C);
|
||||
jv s = jv_string("");
|
||||
jv_dump_term(&C, x, flags, 0, 0, &s);
|
||||
jvp_dtoa_context_free(&C);
|
||||
return s;
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
#include <stdio.h>
|
||||
#include <assert.h>
|
||||
#include "jv_unicode.h"
|
||||
#include "jv_utf8_tables.gen.h"
|
||||
|
||||
const char* jvp_utf8_next(const char* in, const char* end, int* codepoint) {
|
||||
if (in == end) {
|
||||
codepoint = 0;
|
||||
return 0;
|
||||
}
|
||||
unsigned char first = (unsigned char)in[0];
|
||||
int length = utf8_coding_length[first];
|
||||
if (length == 0 || length == UTF8_CONTINUATION_BYTE || in + length > end) {
|
||||
*codepoint = -1;
|
||||
return 0;
|
||||
}
|
||||
*codepoint = ((unsigned)in[0]) & utf8_coding_bits[first];
|
||||
for (int i=1; i<length; i++) {
|
||||
int ch = (unsigned char)in[i];
|
||||
if (utf8_coding_length[(unsigned char)in[i]] != UTF8_CONTINUATION_BYTE){
|
||||
*codepoint = -1;
|
||||
return 0;
|
||||
}
|
||||
*codepoint = (*codepoint << 6) | (ch & 0x3f);
|
||||
}
|
||||
return in + length;
|
||||
}
|
||||
|
||||
int jvp_utf8_verify(const char* in, const char* end) {
|
||||
int codepoint = 0;
|
||||
while ((in = jvp_utf8_next(in, end, &codepoint))) {
|
||||
if (codepoint == -1) return 0;
|
||||
}
|
||||
return codepoint != -1;
|
||||
}
|
||||
|
||||
int jvp_utf8_encode_length(int codepoint) {
|
||||
if (codepoint <= 0x7F) return 1;
|
||||
else if (codepoint <= 0x7FF) return 2;
|
||||
else if (codepoint <= 0xFFFF) return 3;
|
||||
else return 4;
|
||||
}
|
||||
|
||||
int jvp_utf8_encode(int codepoint, char* out) {
|
||||
assert(codepoint >= 0 && codepoint <= 0x10FFFF);
|
||||
char* start = out;
|
||||
if (codepoint <= 0x7F) {
|
||||
*out++ = codepoint;
|
||||
} else if (codepoint <= 0x7FF) {
|
||||
*out++ = 0xC0 + ((codepoint & 0x7C0) >> 6);
|
||||
*out++ = 0x80 + ((codepoint & 0x03F));
|
||||
} else if(codepoint <= 0xFFFF) {
|
||||
*out++ = 0xE0 + ((codepoint & 0xF000) >> 12);
|
||||
*out++ = 0x80 + ((codepoint & 0x0FC0) >> 6);
|
||||
*out++ = 0x80 + ((codepoint & 0x003F));
|
||||
} else {
|
||||
*out++ = 0xF0 + ((codepoint & 0x1C0000) >> 18);
|
||||
*out++ = 0x80 + ((codepoint & 0x03F000) >> 12);
|
||||
*out++ = 0x80 + ((codepoint & 0x000FC0) >> 6);
|
||||
*out++ = 0x80 + ((codepoint & 0x00003F));
|
||||
}
|
||||
assert(out - start == jvp_utf8_encode_length(codepoint));
|
||||
return out - start;
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
#ifndef JV_UNICODE_H
|
||||
#define JV_UNICODE_H
|
||||
|
||||
const char* jvp_utf8_next(const char* in, const char* end, int* codepoint);
|
||||
|
||||
|
||||
int jvp_utf8_decode_length(char startchar);
|
||||
|
||||
int jvp_utf8_encode_length(int codepoint);
|
||||
int jvp_utf8_encode(int codepoint, char* out);
|
||||
#endif
|
||||
@@ -0,0 +1,167 @@
|
||||
%{
|
||||
#include "jv_alloc.h"
|
||||
#include "compile.h"
|
||||
|
||||
struct lexer_param;
|
||||
|
||||
#include "parser.gen.h" /* Generated by bison. */
|
||||
|
||||
#define YY_USER_ACTION \
|
||||
do { \
|
||||
yylloc->start = yyget_extra(yyscanner); \
|
||||
yylloc->end = yylloc->start + yyleng; \
|
||||
yyset_extra(yylloc->end, yyscanner); \
|
||||
} while (0);
|
||||
|
||||
%}
|
||||
|
||||
%s IN_PAREN
|
||||
%s IN_BRACKET
|
||||
%s IN_BRACE
|
||||
%s IN_QQINTERP
|
||||
%x IN_QQSTRING
|
||||
%{
|
||||
static int enter(int opening, int state, yyscan_t yyscanner);
|
||||
static int try_exit(int closing, int state, yyscan_t yyscanner);
|
||||
%}
|
||||
|
||||
%option noyywrap nounput noinput nodefault
|
||||
%option noyyalloc noyyrealloc noyyfree
|
||||
%option reentrant
|
||||
%option extra-type="int"
|
||||
%option bison-bridge bison-locations
|
||||
%option prefix="jq_yy"
|
||||
%option stack
|
||||
|
||||
|
||||
%%
|
||||
|
||||
"#"[^\r\n]* { /* comments */ }
|
||||
|
||||
"!=" { return NEQ; }
|
||||
"==" { return EQ; }
|
||||
"as" { return AS; }
|
||||
"def" { return DEF; }
|
||||
"if" { return IF; }
|
||||
"then" { return THEN; }
|
||||
"else" { return ELSE; }
|
||||
"elif" { return ELSE_IF; }
|
||||
"and" { return AND; }
|
||||
"or" { return OR; }
|
||||
"end" { return END; }
|
||||
"//" { return DEFINEDOR; }
|
||||
"|=" { return SETPIPE; }
|
||||
"+=" { return SETPLUS; }
|
||||
"-=" { return SETMINUS; }
|
||||
"*=" { return SETMULT; }
|
||||
"/=" { return SETDIV; }
|
||||
"//=" { return SETDEFINEDOR; }
|
||||
"<=" { return LESSEQ; }
|
||||
">=" { return GREATEREQ; }
|
||||
"."|"="|";"|","|":"|"|"|"+"|"-"|"*"|"/"|"\$"|"<"|">" { return yytext[0];}
|
||||
|
||||
"["|"{"|"(" {
|
||||
return enter(yytext[0], YY_START, yyscanner);
|
||||
}
|
||||
|
||||
"]"|"}"|")" {
|
||||
return try_exit(yytext[0], YY_START, yyscanner);
|
||||
}
|
||||
|
||||
|
||||
-?[0-9.]+([eE][+-]?[0-9]+)? {
|
||||
yylval->literal = jv_parse_sized(yytext, yyleng); return LITERAL;
|
||||
}
|
||||
|
||||
"\"" {
|
||||
yy_push_state(IN_QQSTRING, yyscanner);
|
||||
return QQSTRING_START;
|
||||
}
|
||||
|
||||
<IN_QQSTRING>{
|
||||
"\\(" {
|
||||
return enter(QQSTRING_INTERP_START, YY_START, yyscanner);
|
||||
}
|
||||
"\"" {
|
||||
yy_pop_state(yyscanner);
|
||||
return QQSTRING_END;
|
||||
}
|
||||
(\\[^u(]|\\u[a-zA-Z0-9]{0,4})+ {
|
||||
/* pass escapes to the json parser */
|
||||
jv escapes = jv_string_fmt("\"%.*s\"", yyleng, yytext);
|
||||
yylval->literal = jv_parse_sized(jv_string_value(escapes), jv_string_length(jv_copy(escapes)));
|
||||
jv_free(escapes);
|
||||
return QQSTRING_TEXT;
|
||||
}
|
||||
[^\\\"]+ {
|
||||
yylval->literal = jv_string_sized(yytext, yyleng);
|
||||
return QQSTRING_TEXT;
|
||||
}
|
||||
. {
|
||||
return INVALID_CHARACTER;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
[a-zA-Z_][a-zA-Z_0-9]* { yylval->literal = jv_string(yytext); return IDENT;}
|
||||
|
||||
[ \n\t]+ {}
|
||||
|
||||
. { return INVALID_CHARACTER; }
|
||||
|
||||
%%
|
||||
/* perhaps these should be calls... */
|
||||
/*
|
||||
"true" { return TRUE; }
|
||||
"false" { return FALSE; }
|
||||
"null" { return NULL; }
|
||||
*/
|
||||
static int try_exit(int c, int state, yyscan_t yyscanner) {
|
||||
char match = 0;
|
||||
int ret;
|
||||
switch (state) {
|
||||
case IN_PAREN: match = ret = ')'; break;
|
||||
case IN_BRACKET: match = ret = ']'; break;
|
||||
case IN_BRACE: match = ret = '}'; break;
|
||||
|
||||
case IN_QQINTERP:
|
||||
match = ')';
|
||||
ret = QQSTRING_INTERP_END;
|
||||
break;
|
||||
|
||||
default:
|
||||
// may not be the best error to give
|
||||
return INVALID_CHARACTER;
|
||||
}
|
||||
assert(match);
|
||||
if (match == c) {
|
||||
yy_pop_state(yyscanner);
|
||||
return ret;
|
||||
} else {
|
||||
// FIXME: should we pop? Give a better error at least
|
||||
return INVALID_CHARACTER;
|
||||
}
|
||||
}
|
||||
|
||||
static int enter(int c, int currstate, yyscan_t yyscanner) {
|
||||
int state = 0;
|
||||
switch (c) {
|
||||
case '(': state = IN_PAREN; break;
|
||||
case '[': state = IN_BRACKET; break;
|
||||
case '{': state = IN_BRACE; break;
|
||||
case QQSTRING_INTERP_START: state = IN_QQINTERP; break;
|
||||
}
|
||||
assert(state);
|
||||
yy_push_state(state, yyscanner);
|
||||
return c;
|
||||
}
|
||||
|
||||
void* yyalloc(size_t sz, void* extra) {
|
||||
return jv_mem_alloc(sz);
|
||||
}
|
||||
void* yyrealloc(void* p, size_t sz, void* extra) {
|
||||
return jv_mem_realloc(p, sz);
|
||||
}
|
||||
void yyfree(void* p, void* extra) {
|
||||
jv_mem_free(p);
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
#ifndef _LOCFILE_H
|
||||
#define _LOCFILE_H
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <assert.h>
|
||||
#include <stdarg.h>
|
||||
#include "jv_alloc.h"
|
||||
typedef struct {
|
||||
int start, end;
|
||||
} location;
|
||||
|
||||
static const location UNKNOWN_LOCATION = {-1, -1};
|
||||
|
||||
struct locfile {
|
||||
const char* data;
|
||||
int length;
|
||||
int* linemap;
|
||||
int nlines;
|
||||
};
|
||||
|
||||
static void locfile_init(struct locfile* l, const char* data, int length) {
|
||||
l->data = data;
|
||||
l->length = length;
|
||||
l->nlines = 1;
|
||||
for (int i=0; i<length; i++) {
|
||||
if (data[i] == '\n') l->nlines++;
|
||||
}
|
||||
l->linemap = jv_mem_alloc(sizeof(int) * (l->nlines + 1));
|
||||
l->linemap[0] = 0;
|
||||
int line = 1;
|
||||
for (int i=0; i<length; i++) {
|
||||
if (data[i] == '\n') {
|
||||
l->linemap[line] = i;
|
||||
line++;
|
||||
}
|
||||
}
|
||||
l->linemap[l->nlines] = length;
|
||||
}
|
||||
|
||||
static void locfile_free(struct locfile* l) {
|
||||
jv_mem_free(l->linemap);
|
||||
}
|
||||
|
||||
static int locfile_get_line(struct locfile* l, int pos) {
|
||||
assert(pos < l->length);
|
||||
int line = 0;
|
||||
while (l->linemap[line+1] < pos) line++;
|
||||
assert(line < l->nlines);
|
||||
return line;
|
||||
}
|
||||
|
||||
static int locfile_line_length(struct locfile* l, int line) {
|
||||
assert(line < l->nlines);
|
||||
return l->linemap[line+1] - l->linemap[line];
|
||||
}
|
||||
|
||||
static void locfile_locate(struct locfile* l, location loc, const char* fmt, ...) {
|
||||
va_list fmtargs;
|
||||
va_start(fmtargs, fmt);
|
||||
vfprintf(stderr, fmt, fmtargs);
|
||||
va_end(fmtargs);
|
||||
fprintf(stderr, "\n");
|
||||
if (loc.start == -1) {
|
||||
fprintf(stderr, "<unknown location>\n");
|
||||
return;
|
||||
}
|
||||
int startline = locfile_get_line(l, loc.start);
|
||||
int offset = l->linemap[startline];
|
||||
fprintf(stderr, "%.*s\n", locfile_line_length(l, startline), l->data + offset);
|
||||
fprintf(stderr, "%*s", loc.start - offset, "");
|
||||
for (int i = loc.start;
|
||||
i < loc.end && i < offset + locfile_line_length(l, startline);
|
||||
i++){
|
||||
fprintf(stderr, "^");
|
||||
}
|
||||
fprintf(stderr, "\n");
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,269 @@
|
||||
#define _POSIX_SOURCE
|
||||
#include <stdio.h>
|
||||
#include <errno.h>
|
||||
#include <string.h>
|
||||
#include <ctype.h>
|
||||
#include <unistd.h>
|
||||
#include "compile.h"
|
||||
#include "builtin.h"
|
||||
#include "jv.h"
|
||||
#include "jv_parse.h"
|
||||
#include "locfile.h"
|
||||
#include "parser.h"
|
||||
#include "execute.h"
|
||||
#include "jv_alloc.h"
|
||||
#include "version.gen.h"
|
||||
|
||||
static const char* progname;
|
||||
|
||||
static void usage() {
|
||||
fprintf(stderr, "\njq - commandline JSON processor [version %s]\n", JQ_VERSION);
|
||||
fprintf(stderr, "Usage: %s [options] <jq filter> [file...]\n\n", progname);
|
||||
fprintf(stderr, "For a description of the command line options and\n");
|
||||
fprintf(stderr, "how to write jq filters (and why you might want to)\n");
|
||||
fprintf(stderr, "see the jq manpage, or the online documentation at\n");
|
||||
fprintf(stderr, "http://stedolan.github.com/jq\n\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
static void die() {
|
||||
fprintf(stderr, "Use %s --help for help with command-line options,\n", progname);
|
||||
fprintf(stderr, "or see the jq documentation at http://stedolan.github.com/jq\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
static int isoptish(const char* text) {
|
||||
return text[0] == '-' && (text[1] == '-' || isalpha(text[1]));
|
||||
}
|
||||
|
||||
static int isoption(const char* text, char shortopt, const char* longopt) {
|
||||
if (text[0] != '-') return 0;
|
||||
if (strlen(text) == 2 && text[1] == shortopt) return 1;
|
||||
if (text[1] == '-' && !strcmp(text+2, longopt)) return 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
enum {
|
||||
SLURP = 1,
|
||||
RAW_INPUT = 2,
|
||||
PROVIDE_NULL = 4,
|
||||
|
||||
RAW_OUTPUT = 8,
|
||||
COMPACT_OUTPUT = 16,
|
||||
ASCII_OUTPUT = 32,
|
||||
COLOUR_OUTPUT = 64,
|
||||
NO_COLOUR_OUTPUT = 128,
|
||||
|
||||
FROM_FILE = 256,
|
||||
};
|
||||
static int options = 0;
|
||||
static struct bytecode* bc;
|
||||
|
||||
static void process(jv value) {
|
||||
jq_init(bc, value);
|
||||
jv result;
|
||||
while (jv_is_valid(result = jq_next())) {
|
||||
if ((options & RAW_OUTPUT) && jv_get_kind(result) == JV_KIND_STRING) {
|
||||
fwrite(jv_string_value(result), 1, jv_string_length(jv_copy(result)), stdout);
|
||||
} else {
|
||||
int dumpopts;
|
||||
#ifdef JQ_DEFAULT_ENABLE_COLOR
|
||||
dumpopts = JQ_DEFAULT_ENABLE_COLOR ? JV_PRINT_COLOUR : 0;
|
||||
#else
|
||||
dumpopts = isatty(fileno(stdout)) ? JV_PRINT_COLOUR : 0;
|
||||
#endif
|
||||
if (!(options & COMPACT_OUTPUT)) dumpopts |= JV_PRINT_PRETTY;
|
||||
if (options & ASCII_OUTPUT) dumpopts |= JV_PRINT_ASCII;
|
||||
if (options & COLOUR_OUTPUT) dumpopts |= JV_PRINT_COLOUR;
|
||||
if (options & NO_COLOUR_OUTPUT) dumpopts &= ~JV_PRINT_COLOUR;
|
||||
jv_dump(result, dumpopts);
|
||||
}
|
||||
printf("\n");
|
||||
}
|
||||
jv_free(result);
|
||||
jq_teardown();
|
||||
}
|
||||
|
||||
static jv slurp_file(const char* filename) {
|
||||
FILE* file = fopen(filename, "r");
|
||||
if (!file) {
|
||||
return jv_invalid_with_msg(jv_string_fmt("Could not open %s: %s",
|
||||
filename,
|
||||
strerror(errno)));
|
||||
}
|
||||
jv data = jv_string("");
|
||||
while (!feof(file) && !ferror(file)) {
|
||||
char buf[4096];
|
||||
size_t n = fread(buf, 1, sizeof(buf), file);
|
||||
data = jv_string_concat(data, jv_string_sized(buf, (int)n));
|
||||
}
|
||||
int badread = ferror(file);
|
||||
fclose(file);
|
||||
if (badread) {
|
||||
jv_free(data);
|
||||
return jv_invalid_with_msg(jv_string_fmt("Error reading from %s",
|
||||
filename));
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
FILE* current_input;
|
||||
const char** input_filenames;
|
||||
int ninput_files;
|
||||
int next_input_idx;
|
||||
static int read_more(char* buf, size_t size) {
|
||||
while (!current_input || feof(current_input)) {
|
||||
if (current_input) {
|
||||
fclose(current_input);
|
||||
current_input = 0;
|
||||
}
|
||||
if (next_input_idx == ninput_files) {
|
||||
return 0;
|
||||
}
|
||||
if (!strcmp(input_filenames[next_input_idx], "-")) {
|
||||
current_input = stdin;
|
||||
} else {
|
||||
current_input = fopen(input_filenames[next_input_idx], "r");
|
||||
}
|
||||
if (!current_input) {
|
||||
fprintf(stderr, "%s: %s: %s\n", progname, input_filenames[next_input_idx], strerror(errno));
|
||||
}
|
||||
next_input_idx++;
|
||||
}
|
||||
|
||||
if (!fgets(buf, sizeof(buf), current_input)) buf[0] = 0;
|
||||
return 1;
|
||||
}
|
||||
|
||||
int main(int argc, char* argv[]) {
|
||||
if (argc) progname = argv[0];
|
||||
|
||||
const char* program = 0;
|
||||
input_filenames = jv_mem_alloc(sizeof(const char*) * argc);
|
||||
ninput_files = 0;
|
||||
int further_args_are_files = 0;
|
||||
for (int i=1; i<argc; i++) {
|
||||
if (further_args_are_files) {
|
||||
input_filenames[ninput_files++] = argv[i];
|
||||
} else if (!strcmp(argv[i], "--")) {
|
||||
if (!program) usage();
|
||||
further_args_are_files = 1;
|
||||
} else if (!isoptish(argv[i])) {
|
||||
if (program) {
|
||||
input_filenames[ninput_files++] = argv[i];
|
||||
} else {
|
||||
program = argv[i];
|
||||
}
|
||||
} else if (isoption(argv[i], 's', "slurp")) {
|
||||
options |= SLURP;
|
||||
} else if (isoption(argv[i], 'r', "raw-output")) {
|
||||
options |= RAW_OUTPUT;
|
||||
} else if (isoption(argv[i], 'c', "compact-output")) {
|
||||
options |= COMPACT_OUTPUT;
|
||||
} else if (isoption(argv[i], 'C', "color-output")) {
|
||||
options |= COLOUR_OUTPUT;
|
||||
} else if (isoption(argv[i], 'M', "monochrome-output")) {
|
||||
options |= NO_COLOUR_OUTPUT;
|
||||
} else if (isoption(argv[i], 'a', "ascii-output")) {
|
||||
options |= ASCII_OUTPUT;
|
||||
} else if (isoption(argv[i], 'R', "raw-input")) {
|
||||
options |= RAW_INPUT;
|
||||
} else if (isoption(argv[i], 'n', "null-input")) {
|
||||
options |= PROVIDE_NULL;
|
||||
} else if (isoption(argv[i], 'f', "from-file")) {
|
||||
options |= FROM_FILE;
|
||||
} else if (isoption(argv[i], 'h', "help")) {
|
||||
usage();
|
||||
} else if (isoption(argv[i], 'V', "version")) {
|
||||
fprintf(stderr, "jq version %s\n", JQ_VERSION);
|
||||
return 0;
|
||||
} else {
|
||||
fprintf(stderr, "%s: Unknown option %s\n", progname, argv[i]);
|
||||
die();
|
||||
}
|
||||
}
|
||||
if (!program) usage();
|
||||
if (ninput_files == 0) current_input = stdin;
|
||||
|
||||
if ((options & PROVIDE_NULL) && (options & (RAW_INPUT | SLURP))) {
|
||||
fprintf(stderr, "%s: --null-input cannot be used with --raw-input or --slurp\n", progname);
|
||||
die();
|
||||
}
|
||||
|
||||
if (options & FROM_FILE) {
|
||||
jv data = slurp_file(program);
|
||||
if (!jv_is_valid(data)) {
|
||||
data = jv_invalid_get_msg(data);
|
||||
fprintf(stderr, "%s: %s\n", progname, jv_string_value(data));
|
||||
jv_free(data);
|
||||
return 1;
|
||||
}
|
||||
bc = jq_compile(jv_string_value(data));
|
||||
jv_free(data);
|
||||
} else {
|
||||
bc = jq_compile(program);
|
||||
}
|
||||
if (!bc) return 1;
|
||||
|
||||
#if JQ_DEBUG
|
||||
dump_disassembly(0, bc);
|
||||
printf("\n");
|
||||
#endif
|
||||
|
||||
if (options & PROVIDE_NULL) {
|
||||
process(jv_null());
|
||||
} else {
|
||||
jv slurped;
|
||||
if (options & SLURP) {
|
||||
if (options & RAW_INPUT) {
|
||||
slurped = jv_string("");
|
||||
} else {
|
||||
slurped = jv_array();
|
||||
}
|
||||
}
|
||||
struct jv_parser parser;
|
||||
jv_parser_init(&parser);
|
||||
char buf[4096];
|
||||
while (read_more(buf, sizeof(buf))) {
|
||||
if (options & RAW_INPUT) {
|
||||
int len = strlen(buf);
|
||||
if (len > 0) {
|
||||
if (options & SLURP) {
|
||||
slurped = jv_string_concat(slurped, jv_string(buf));
|
||||
} else {
|
||||
if (buf[len-1] == '\n') buf[len-1] = 0;
|
||||
process(jv_string(buf));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
jv_parser_set_buf(&parser, buf, strlen(buf), !feof(stdin));
|
||||
jv value;
|
||||
while (jv_is_valid((value = jv_parser_next(&parser)))) {
|
||||
if (options & SLURP) {
|
||||
slurped = jv_array_append(slurped, value);
|
||||
} else {
|
||||
process(value);
|
||||
}
|
||||
}
|
||||
if (jv_invalid_has_msg(jv_copy(value))) {
|
||||
jv msg = jv_invalid_get_msg(value);
|
||||
fprintf(stderr, "parse error: %s\n", jv_string_value(msg));
|
||||
jv_free(msg);
|
||||
break;
|
||||
} else {
|
||||
jv_free(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
jv_parser_free(&parser);
|
||||
if (options & SLURP) {
|
||||
process(slurped);
|
||||
}
|
||||
}
|
||||
jv_mem_free(input_filenames);
|
||||
bytecode_free(bc);
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
#include "opcode.h"
|
||||
|
||||
// flags, length
|
||||
#define NONE 0, 1
|
||||
#define CONSTANT OP_HAS_CONSTANT, 2
|
||||
#define VARIABLE (OP_HAS_VARIABLE | OP_HAS_BINDING), 3
|
||||
#define BRANCH OP_HAS_BRANCH, 2
|
||||
#define CFUNC (OP_HAS_CFUNC | OP_HAS_BINDING), 3
|
||||
#define UFUNC (OP_HAS_UFUNC | OP_HAS_BINDING | OP_IS_CALL_PSEUDO), 4
|
||||
#define DEFINITION (OP_IS_CALL_PSEUDO | OP_HAS_BINDING), 0
|
||||
#define CLOSURE_REF_IMM (OP_IS_CALL_PSEUDO | OP_HAS_BINDING), 2
|
||||
|
||||
#define OP(name, imm, in, out) \
|
||||
{name, #name, imm, in, out},
|
||||
|
||||
static const struct opcode_description opcode_descriptions[] = {
|
||||
#include "opcode_list.h"
|
||||
};
|
||||
|
||||
static const struct opcode_description invalid_opcode_description = {
|
||||
-1, "#INVALID", 0, 0, 0, 0
|
||||
};
|
||||
|
||||
|
||||
const struct opcode_description* opcode_describe(opcode op) {
|
||||
if ((int)op >= 0 && (int)op < NUM_OPCODES) {
|
||||
return &opcode_descriptions[op];
|
||||
} else {
|
||||
return &invalid_opcode_description;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
#ifndef OPCODE_H
|
||||
#define OPCODE_H
|
||||
#include <assert.h>
|
||||
|
||||
typedef enum {
|
||||
#define OP(name, imm, in, out) name,
|
||||
#include "opcode_list.h"
|
||||
#undef OP
|
||||
} opcode;
|
||||
|
||||
enum {
|
||||
NUM_OPCODES =
|
||||
#define OP(name, imm, in, out) +1
|
||||
#include "opcode_list.h"
|
||||
#undef OP
|
||||
};
|
||||
|
||||
enum {
|
||||
OP_HAS_CONSTANT = 2,
|
||||
OP_HAS_VARIABLE = 4,
|
||||
OP_HAS_BRANCH = 8,
|
||||
OP_HAS_CFUNC = 32,
|
||||
OP_HAS_UFUNC = 64,
|
||||
OP_IS_CALL_PSEUDO = 128,
|
||||
OP_HAS_BINDING = 1024,
|
||||
};
|
||||
struct opcode_description {
|
||||
opcode op;
|
||||
const char* name;
|
||||
|
||||
int flags;
|
||||
|
||||
// length in 16-bit units
|
||||
int length;
|
||||
|
||||
int stack_in, stack_out;
|
||||
};
|
||||
|
||||
const struct opcode_description* opcode_describe(opcode op);
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,28 @@
|
||||
OP(LOADK, CONSTANT, 1, 1)
|
||||
OP(DUP, NONE, 1, 2)
|
||||
OP(DUP2, NONE, 2, 3)
|
||||
OP(SWAP, NONE, 2, 2)
|
||||
OP(POP, NONE, 1, 0)
|
||||
OP(LOADV, VARIABLE, 1, 1)
|
||||
OP(STOREV, VARIABLE, 1, 0)
|
||||
OP(INDEX, NONE, 2, 1)
|
||||
OP(YIELD, NONE, 1, 0)
|
||||
OP(EACH, NONE, 1, 1)
|
||||
OP(FORK, BRANCH, 0, 0)
|
||||
OP(JUMP, BRANCH, 0, 0)
|
||||
OP(JUMP_F,BRANCH, 1, 0)
|
||||
OP(BACKTRACK, NONE, 0, 0)
|
||||
OP(APPEND, NONE, 2, 1)
|
||||
OP(INSERT, NONE, 4, 2)
|
||||
|
||||
OP(ASSIGN, VARIABLE, 3, 0)
|
||||
|
||||
OP(CALL_BUILTIN, CFUNC, -1, 1)
|
||||
|
||||
OP(CALL_JQ, UFUNC, 1, 1)
|
||||
OP(RET, NONE, 1, 1)
|
||||
|
||||
OP(CLOSURE_PARAM, DEFINITION, 0, 0)
|
||||
OP(CLOSURE_REF, CLOSURE_REF_IMM, 0, 0)
|
||||
OP(CLOSURE_CREATE, DEFINITION, 0, 0)
|
||||
OP(CLOSURE_CREATE_C, DEFINITION, 0, 0)
|
||||
@@ -0,0 +1,7 @@
|
||||
#ifndef PARSER_H
|
||||
#define PARSER_H
|
||||
|
||||
int jq_parse(struct locfile* source, block* answer);
|
||||
int jq_parse_library(struct locfile* locations, block* answer);
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,460 @@
|
||||
%{
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include "compile.h"
|
||||
#include "jv_alloc.h"
|
||||
#define YYMALLOC jv_mem_alloc
|
||||
#define YYFREE jv_mem_free
|
||||
|
||||
struct lexer_param;
|
||||
|
||||
%}
|
||||
%code requires {
|
||||
#include "locfile.h"
|
||||
#define YYLTYPE location
|
||||
#define YYLLOC_DEFAULT(Loc, Rhs, N) \
|
||||
do { \
|
||||
if (N) { \
|
||||
(Loc).start = YYRHSLOC(Rhs, 1).start; \
|
||||
(Loc).end = YYRHSLOC(Rhs, N).end; \
|
||||
} else { \
|
||||
(Loc).start = YYRHSLOC(Rhs, 0).end; \
|
||||
(Loc).end = YYRHSLOC(Rhs, 0).end; \
|
||||
} \
|
||||
} while (0)
|
||||
}
|
||||
|
||||
%locations
|
||||
%error-verbose
|
||||
%define api.pure
|
||||
%union {
|
||||
jv literal;
|
||||
block blk;
|
||||
}
|
||||
|
||||
%destructor { jv_free($$); } <literal>
|
||||
%destructor { block_free($$); } <blk>
|
||||
|
||||
%parse-param {block* answer}
|
||||
%parse-param {int* errors}
|
||||
%parse-param {struct locfile* locations}
|
||||
%parse-param {struct lexer_param* lexer_param_ptr}
|
||||
%lex-param {block* answer}
|
||||
%lex-param {int* errors}
|
||||
%lex-param {struct locfile* locations}
|
||||
%lex-param {struct lexer_param* lexer_param_ptr}
|
||||
|
||||
|
||||
%token INVALID_CHARACTER
|
||||
%token <literal> IDENT
|
||||
%token <literal> LITERAL
|
||||
%token EQ "=="
|
||||
%token NEQ "!="
|
||||
%token DEFINEDOR "//"
|
||||
%token AS "as"
|
||||
%token DEF "def"
|
||||
%token IF "if"
|
||||
%token THEN "then"
|
||||
%token ELSE "else"
|
||||
%token ELSE_IF "elif"
|
||||
%token END "end"
|
||||
%token AND "and"
|
||||
%token OR "or"
|
||||
%token SETPIPE "|="
|
||||
%token SETPLUS "+="
|
||||
%token SETMINUS "-="
|
||||
%token SETMULT "*="
|
||||
%token SETDIV "/="
|
||||
%token SETDEFINEDOR "//="
|
||||
%token LESSEQ "<="
|
||||
%token GREATEREQ ">="
|
||||
|
||||
%token QQSTRING_START
|
||||
%token <literal> QQSTRING_TEXT
|
||||
%token QQSTRING_INTERP_START
|
||||
%token QQSTRING_INTERP_END
|
||||
%token QQSTRING_END
|
||||
|
||||
/* revolting hack */
|
||||
%left ';'
|
||||
%right '|'
|
||||
%left ','
|
||||
%right "//"
|
||||
%nonassoc '=' SETPIPE SETPLUS SETMINUS SETMULT SETDIV SETDEFINEDOR
|
||||
%left OR
|
||||
%left AND
|
||||
%nonassoc NEQ EQ '<' '>' LESSEQ GREATEREQ
|
||||
%left '+' '-'
|
||||
%left '*' '/'
|
||||
|
||||
|
||||
%type <blk> Exp Term MkDict MkDictPair ExpD ElseBody QQString FuncDef FuncDefs String
|
||||
%{
|
||||
#include "lexer.gen.h"
|
||||
struct lexer_param {
|
||||
yyscan_t lexer;
|
||||
};
|
||||
#define FAIL(loc, msg) \
|
||||
do { \
|
||||
location l = loc; \
|
||||
yyerror(&l, answer, errors, locations, lexer_param_ptr, msg); \
|
||||
/*YYERROR*/; \
|
||||
} while (0)
|
||||
|
||||
void yyerror(YYLTYPE* loc, block* answer, int* errors,
|
||||
struct locfile* locations, struct lexer_param* lexer_param_ptr, const char *s){
|
||||
(*errors)++;
|
||||
locfile_locate(locations, *loc, "error: %s", s);
|
||||
}
|
||||
|
||||
int yylex(YYSTYPE* yylval, YYLTYPE* yylloc, block* answer, int* errors,
|
||||
struct locfile* locations, struct lexer_param* lexer_param_ptr) {
|
||||
yyscan_t lexer = lexer_param_ptr->lexer;
|
||||
while (1) {
|
||||
int tok = jq_yylex(yylval, yylloc, lexer);
|
||||
if (tok == INVALID_CHARACTER) {
|
||||
FAIL(*yylloc, "Invalid character");
|
||||
} else {
|
||||
if ((tok == LITERAL || tok == QQSTRING_TEXT) && !jv_is_valid(yylval->literal)) {
|
||||
jv msg = jv_invalid_get_msg(jv_copy(yylval->literal));
|
||||
if (jv_get_kind(msg) == JV_KIND_STRING) {
|
||||
FAIL(*yylloc, jv_string_value(msg));
|
||||
} else {
|
||||
FAIL(*yylloc, "Invalid literal");
|
||||
}
|
||||
jv_free(msg);
|
||||
jv_free(yylval->literal);
|
||||
yylval->literal = jv_null();
|
||||
}
|
||||
return tok;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static block gen_dictpair(block k, block v) {
|
||||
return BLOCK(gen_subexp(k), gen_subexp(v), gen_op_simple(INSERT));
|
||||
}
|
||||
|
||||
static block gen_index(block obj, block key) {
|
||||
return BLOCK(gen_subexp(key), obj, gen_op_simple(INDEX));
|
||||
}
|
||||
|
||||
static block gen_binop(block a, block b, int op) {
|
||||
const char* funcname = 0;
|
||||
switch (op) {
|
||||
case '+': funcname = "_plus"; break;
|
||||
case '-': funcname = "_minus"; break;
|
||||
case '*': funcname = "_multiply"; break;
|
||||
case '/': funcname = "_divide"; break;
|
||||
case EQ: funcname = "_equal"; break;
|
||||
case NEQ: funcname = "_notequal"; break;
|
||||
case '<': funcname = "_less"; break;
|
||||
case '>': funcname = "_greater"; break;
|
||||
case LESSEQ: funcname = "_lesseq"; break;
|
||||
case GREATEREQ: funcname = "_greatereq"; break;
|
||||
}
|
||||
assert(funcname);
|
||||
|
||||
return gen_call(funcname, BLOCK(gen_lambda(a), gen_lambda(b)));
|
||||
}
|
||||
|
||||
static block gen_format(block a) {
|
||||
return BLOCK(a, gen_call("tostring", gen_noop()));
|
||||
}
|
||||
|
||||
static block gen_update(block a, block op, int optype) {
|
||||
if (optype) {
|
||||
op = gen_binop(gen_noop(), op, optype);
|
||||
}
|
||||
return gen_assign(BLOCK(a, gen_op_simple(DUP), op));
|
||||
}
|
||||
|
||||
%}
|
||||
|
||||
%%
|
||||
TopLevel:
|
||||
Exp {
|
||||
*answer = $1;
|
||||
} |
|
||||
FuncDefs {
|
||||
*answer = $1;
|
||||
}
|
||||
|
||||
FuncDefs:
|
||||
/* empty */ {
|
||||
$$ = gen_noop();
|
||||
} |
|
||||
FuncDef FuncDefs {
|
||||
$$ = block_join($1, $2);
|
||||
}
|
||||
|
||||
Exp:
|
||||
FuncDef Exp %prec ';' {
|
||||
$$ = block_bind($1, $2, OP_IS_CALL_PSEUDO);
|
||||
} |
|
||||
|
||||
Term "as" '$' IDENT '|' Exp {
|
||||
$$ = BLOCK(gen_op_simple(DUP), $1,
|
||||
block_bind(gen_op_var_unbound(STOREV, jv_string_value($4)),
|
||||
$6, OP_HAS_VARIABLE));
|
||||
jv_free($4);
|
||||
} |
|
||||
|
||||
"if" Exp "then" Exp ElseBody {
|
||||
$$ = gen_cond($2, $4, $5);
|
||||
} |
|
||||
"if" Exp error {
|
||||
FAIL(@$, "Possibly unterminated 'if' statment");
|
||||
$$ = $2;
|
||||
} |
|
||||
|
||||
Exp '=' Exp {
|
||||
$$ = gen_assign(BLOCK(gen_op_simple(DUP), $3, gen_op_simple(SWAP), $1, gen_op_simple(SWAP)));
|
||||
} |
|
||||
|
||||
Exp "or" Exp {
|
||||
$$ = gen_or($1, $3);
|
||||
} |
|
||||
|
||||
Exp "and" Exp {
|
||||
$$ = gen_and($1, $3);
|
||||
} |
|
||||
|
||||
Exp "//" Exp {
|
||||
$$ = gen_definedor($1, $3);
|
||||
} |
|
||||
|
||||
Exp "//=" Exp {
|
||||
$$ = gen_update($1, gen_definedor(gen_noop(), $3), 0);
|
||||
} |
|
||||
|
||||
Exp "|=" Exp {
|
||||
$$ = gen_update($1, $3, 0);
|
||||
} |
|
||||
|
||||
Exp '|' Exp {
|
||||
$$ = block_join($1, $3);
|
||||
} |
|
||||
|
||||
Exp ',' Exp {
|
||||
$$ = gen_both($1, $3);
|
||||
} |
|
||||
|
||||
Exp '+' Exp {
|
||||
$$ = gen_binop($1, $3, '+');
|
||||
} |
|
||||
|
||||
Exp "+=" Exp {
|
||||
$$ = gen_update($1, $3, '+');
|
||||
} |
|
||||
|
||||
Exp '-' Exp {
|
||||
$$ = gen_binop($1, $3, '-');
|
||||
} |
|
||||
|
||||
Exp "-=" Exp {
|
||||
$$ = gen_update($1, $3, '-');
|
||||
} |
|
||||
|
||||
Exp '*' Exp {
|
||||
$$ = gen_binop($1, $3, '*');
|
||||
} |
|
||||
|
||||
Exp "*=" Exp {
|
||||
$$ = gen_update($1, $3, '*');
|
||||
} |
|
||||
|
||||
Exp '/' Exp {
|
||||
$$ = gen_binop($1, $3, '/');
|
||||
} |
|
||||
|
||||
Exp "/=" Exp {
|
||||
$$ = gen_update($1, $3, '/');
|
||||
} |
|
||||
|
||||
Exp "==" Exp {
|
||||
$$ = gen_binop($1, $3, EQ);
|
||||
} |
|
||||
|
||||
Exp "!=" Exp {
|
||||
$$ = gen_binop($1, $3, NEQ);
|
||||
} |
|
||||
|
||||
Exp '<' Exp {
|
||||
$$ = gen_binop($1, $3, '<');
|
||||
} |
|
||||
|
||||
Exp '>' Exp {
|
||||
$$ = gen_binop($1, $3, '>');
|
||||
} |
|
||||
|
||||
Exp "<=" Exp {
|
||||
$$ = gen_binop($1, $3, LESSEQ);
|
||||
} |
|
||||
|
||||
Exp ">=" Exp {
|
||||
$$ = gen_binop($1, $3, GREATEREQ);
|
||||
} |
|
||||
|
||||
Term {
|
||||
$$ = $1;
|
||||
}
|
||||
|
||||
FuncDef:
|
||||
"def" IDENT ':' Exp ';' {
|
||||
$$ = gen_function(jv_string_value($2), gen_noop(), $4);
|
||||
jv_free($2);
|
||||
} |
|
||||
|
||||
"def" IDENT '(' IDENT ')' ':' Exp ';' {
|
||||
$$ = gen_function(jv_string_value($2),
|
||||
gen_op_block_unbound(CLOSURE_PARAM, jv_string_value($4)),
|
||||
$7);
|
||||
jv_free($2);
|
||||
jv_free($4);
|
||||
}
|
||||
|
||||
|
||||
String:
|
||||
QQSTRING_START QQString QQSTRING_END {
|
||||
$$ = $2;
|
||||
}
|
||||
|
||||
QQString:
|
||||
/* empty */ {
|
||||
$$ = gen_const(jv_string(""));
|
||||
} |
|
||||
QQString QQSTRING_TEXT {
|
||||
$$ = gen_binop($1, gen_const($2), '+');
|
||||
} |
|
||||
QQString QQSTRING_INTERP_START Exp QQSTRING_INTERP_END {
|
||||
$$ = gen_binop($1, gen_format($3), '+');
|
||||
}
|
||||
|
||||
|
||||
ElseBody:
|
||||
"elif" Exp "then" Exp ElseBody {
|
||||
$$ = gen_cond($2, $4, $5);
|
||||
} |
|
||||
"else" Exp "end" {
|
||||
$$ = $2;
|
||||
}
|
||||
|
||||
ExpD:
|
||||
ExpD '|' ExpD {
|
||||
$$ = block_join($1, $3);
|
||||
} |
|
||||
|
||||
Term {
|
||||
$$ = $1;
|
||||
}
|
||||
|
||||
|
||||
Term:
|
||||
'.' {
|
||||
$$ = gen_noop();
|
||||
} |
|
||||
Term '.' IDENT {
|
||||
$$ = gen_index($1, gen_const($3));
|
||||
} |
|
||||
'.' IDENT {
|
||||
$$ = gen_index(gen_noop(), gen_const($2));
|
||||
} |
|
||||
/* FIXME: string literals */
|
||||
Term '[' Exp ']' {
|
||||
$$ = gen_index($1, $3);
|
||||
} |
|
||||
Term '[' ']' {
|
||||
$$ = block_join($1, gen_op_simple(EACH));
|
||||
} |
|
||||
LITERAL {
|
||||
$$ = gen_const($1);
|
||||
} |
|
||||
String {
|
||||
$$ = $1;
|
||||
} |
|
||||
'(' Exp ')' {
|
||||
$$ = $2;
|
||||
} |
|
||||
'[' Exp ']' {
|
||||
$$ = gen_collect($2);
|
||||
} |
|
||||
'[' ']' {
|
||||
$$ = gen_const(jv_array());
|
||||
} |
|
||||
'{' MkDict '}' {
|
||||
$$ = BLOCK(gen_subexp(gen_const(jv_object())), $2, gen_op_simple(POP));
|
||||
} |
|
||||
'$' IDENT {
|
||||
$$ = gen_location(@$, gen_op_var_unbound(LOADV, jv_string_value($2)));
|
||||
jv_free($2);
|
||||
} |
|
||||
IDENT {
|
||||
$$ = gen_location(@$, gen_call(jv_string_value($1), gen_noop()));
|
||||
jv_free($1);
|
||||
} |
|
||||
IDENT '(' Exp ')' {
|
||||
$$ = gen_call(jv_string_value($1), gen_lambda($3));
|
||||
$$ = gen_location(@1, $$);
|
||||
jv_free($1);
|
||||
} |
|
||||
'(' error ')' { $$ = gen_noop(); } |
|
||||
'[' error ']' { $$ = gen_noop(); } |
|
||||
Term '[' error ']' { $$ = $1; } |
|
||||
'{' error '}' { $$ = gen_noop(); }
|
||||
|
||||
MkDict:
|
||||
{
|
||||
$$=gen_noop();
|
||||
} |
|
||||
MkDictPair { $$ = $1; }
|
||||
| MkDictPair ',' MkDict { $$=block_join($1, $3); }
|
||||
| error ',' MkDict { $$ = $3; }
|
||||
|
||||
MkDictPair
|
||||
: IDENT ':' ExpD {
|
||||
$$ = gen_dictpair(gen_const($1), $3);
|
||||
}
|
||||
| String ':' ExpD {
|
||||
$$ = gen_dictpair($1, $3);
|
||||
}
|
||||
| String {
|
||||
$$ = gen_dictpair($1, BLOCK(gen_op_simple(POP), gen_op_simple(DUP2),
|
||||
gen_op_simple(DUP2), gen_op_simple(INDEX)));
|
||||
}
|
||||
| IDENT {
|
||||
$$ = gen_dictpair(gen_const(jv_copy($1)),
|
||||
gen_index(gen_noop(), gen_const($1)));
|
||||
}
|
||||
| '(' Exp ')' ':' ExpD {
|
||||
$$ = gen_dictpair($2, $5);
|
||||
}
|
||||
| '(' error ')' ':' ExpD { $$ = $5; }
|
||||
%%
|
||||
|
||||
int jq_parse(struct locfile* locations, block* answer) {
|
||||
struct lexer_param scanner;
|
||||
YY_BUFFER_STATE buf;
|
||||
jq_yylex_init_extra(0, &scanner.lexer);
|
||||
buf = jq_yy_scan_bytes(locations->data, locations->length, scanner.lexer);
|
||||
int errors = 0;
|
||||
*answer = gen_noop();
|
||||
yyparse(answer, &errors, locations, &scanner);
|
||||
jq_yy_delete_buffer(buf, scanner.lexer);
|
||||
jq_yylex_destroy(scanner.lexer);
|
||||
if (errors > 0) {
|
||||
block_free(*answer);
|
||||
*answer = gen_noop();
|
||||
}
|
||||
return errors;
|
||||
}
|
||||
|
||||
int jq_parse_library(struct locfile* locations, block* answer) {
|
||||
int errs = jq_parse(locations, answer);
|
||||
if (errs) return errs;
|
||||
if (!block_has_only_binders(*answer, OP_IS_CALL_PSEUDO)) {
|
||||
locfile_locate(locations, UNKNOWN_LOCATION, "error: library should only have function definitions, not a main expression");
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
def map(f) = [.[] | f];
|
||||
|
||||
def first = .[0];
|
||||
# def last = .[count-1];
|
||||
def next = .[count];
|
||||
|
||||
# ([])[] would be a decent definition of "empty"
|
||||
# except ([]) is defined as syntactic sugar for empty
|
||||
def empty = {}[];
|
||||
|
||||
def sort = [group(.) | .[]];
|
||||
@@ -0,0 +1,475 @@
|
||||
# Tests are groups of three lines: program, input, expected output
|
||||
# Blank lines and lines starting with # are ignored
|
||||
|
||||
#
|
||||
# Simple value tests to check parser. Input is irrelevant
|
||||
#
|
||||
|
||||
true
|
||||
null
|
||||
true
|
||||
|
||||
false
|
||||
null
|
||||
false
|
||||
|
||||
null
|
||||
42
|
||||
null
|
||||
|
||||
1
|
||||
null
|
||||
1
|
||||
|
||||
# FIXME: much more number testing needed
|
||||
|
||||
{}
|
||||
null
|
||||
{}
|
||||
|
||||
[]
|
||||
null
|
||||
[]
|
||||
|
||||
# The input line starts with a 0xFEFF (byte order mark) codepoint
|
||||
# No, there is no reason to have a byte order mark in UTF8 text.
|
||||
# But apparently people do, so jq shouldn't break on it.
|
||||
.
|
||||
"byte order mark"
|
||||
"byte order mark"
|
||||
|
||||
# We test escapes by matching them against Unicode codepoints
|
||||
# FIXME: more tests needed for weird unicode stuff (e.g. utf16 pairs)
|
||||
"Aa\r\n\t\b\f\u03bc"
|
||||
null
|
||||
"Aa\u000d\u000a\u0009\u0008\u000c\u03bc"
|
||||
|
||||
.
|
||||
"Aa\r\n\t\b\f\u03bc"
|
||||
"Aa\u000d\u000a\u0009\u0008\u000c\u03bc"
|
||||
|
||||
"inter\("pol" + "ation")"
|
||||
null
|
||||
"interpolation"
|
||||
|
||||
#
|
||||
# Dictionary construction syntax
|
||||
#
|
||||
|
||||
{a: 1}
|
||||
null
|
||||
{"a":1}
|
||||
|
||||
{a,b,(.d):.a,e:.b}
|
||||
{"a":1, "b":2, "c":3, "d":"c"}
|
||||
{"a":1, "b":2, "c":1, "e":2}
|
||||
|
||||
{"a",b,"a$\(1+1)"}
|
||||
{"a":1, "b":2, "c":3, "a$2":4}
|
||||
{"a":1, "b":2, "a$2":4}
|
||||
|
||||
#
|
||||
# Field access, piping
|
||||
#
|
||||
|
||||
.foo
|
||||
{"foo": 42, "bar": 43}
|
||||
42
|
||||
|
||||
.foo | .bar
|
||||
{"foo": {"bar": 42}, "bar": "badvalue"}
|
||||
42
|
||||
|
||||
.foo.bar
|
||||
{"foo": {"bar": 42}, "bar": "badvalue"}
|
||||
42
|
||||
|
||||
.foo_bar
|
||||
{"foo_bar": 2}
|
||||
2
|
||||
|
||||
.["foo"].bar
|
||||
{"foo": {"bar": 42}, "bar": "badvalue"}
|
||||
42
|
||||
|
||||
|
||||
#
|
||||
# Multiple outputs, iteration
|
||||
#
|
||||
|
||||
.[]
|
||||
[1,2,3]
|
||||
1
|
||||
2
|
||||
3
|
||||
|
||||
1,1
|
||||
[]
|
||||
1
|
||||
1
|
||||
|
||||
1,.
|
||||
[]
|
||||
1
|
||||
[]
|
||||
|
||||
[.]
|
||||
[2]
|
||||
[[2]]
|
||||
|
||||
[[2]]
|
||||
[3]
|
||||
[[2]]
|
||||
|
||||
[{}]
|
||||
[2]
|
||||
[{}]
|
||||
|
||||
[.[]]
|
||||
["a"]
|
||||
["a"]
|
||||
|
||||
[(.,1),((.,.[]),(2,3))]
|
||||
["a","b"]
|
||||
[["a","b"],1,["a","b"],"a","b",2,3]
|
||||
|
||||
[([5,5][]),.,.[]]
|
||||
[1,2,3]
|
||||
[5,5,[1,2,3],1,2,3]
|
||||
|
||||
{x: (1,2)},{x:3} | .x
|
||||
null
|
||||
1
|
||||
2
|
||||
3
|
||||
|
||||
#
|
||||
# Variables
|
||||
#
|
||||
|
||||
1 as $x | 2 as $y | [$x,$y,$x]
|
||||
null
|
||||
[1,2,1]
|
||||
|
||||
[1,2,3][] as $x | [[4,5,6,7][$x]]
|
||||
null
|
||||
[5]
|
||||
[6]
|
||||
[7]
|
||||
|
||||
42 as $x | . | . | . + 432 | $x + 1
|
||||
34324
|
||||
43
|
||||
|
||||
1 as $x | [$x,$x,$x as $x | $x]
|
||||
null
|
||||
[1,1,1]
|
||||
|
||||
# [.,(.[] | {x:.},.),.,.[]]
|
||||
|
||||
#
|
||||
# Builtin functions
|
||||
#
|
||||
|
||||
1+1
|
||||
null
|
||||
2
|
||||
|
||||
1+1
|
||||
"wtasdf"
|
||||
2.0
|
||||
|
||||
1e+0+0.001e3
|
||||
"I wonder what this will be?"
|
||||
20e-1
|
||||
|
||||
.+4
|
||||
15
|
||||
19.0
|
||||
|
||||
[1,2,3] + [.]
|
||||
null
|
||||
[1,2,3,null]
|
||||
|
||||
{"a":1} + {"b":2} + {"c":3}
|
||||
"asdfasdf"
|
||||
{"a":1, "b":2, "c":3}
|
||||
|
||||
"asdf" + "jkl;" + . + . + .
|
||||
"some string"
|
||||
"asdfjkl;some stringsome stringsome string"
|
||||
|
||||
"\u0000\u0020\u0000" + .
|
||||
"\u0000\u0020\u0000"
|
||||
"\u0000 \u0000\u0000 \u0000"
|
||||
|
||||
42 - .
|
||||
11
|
||||
31
|
||||
|
||||
[1,2,3,4,1] - [.,3]
|
||||
1
|
||||
[2,4]
|
||||
|
||||
[10 * 20, 20 / .]
|
||||
4
|
||||
[200, 5]
|
||||
|
||||
1 + 2 * 2 + 10 / 2
|
||||
null
|
||||
10
|
||||
|
||||
[16 / 4 / 2, 16 / 4 * 2, 16 - 4 - 2, 16 - 4 + 2]
|
||||
null
|
||||
[2, 8, 10, 14]
|
||||
|
||||
1 + tonumber + ("10" | tonumber)
|
||||
4
|
||||
15
|
||||
|
||||
[{"a":42},.object,10,.num,false,true,null,"b",[1,4]] | .[] as $x | [$x == .[]]
|
||||
{"object": {"a":42}, "num":10.0}
|
||||
[true, true, false, false, false, false, false, false, false]
|
||||
[true, true, false, false, false, false, false, false, false]
|
||||
[false, false, true, true, false, false, false, false, false]
|
||||
[false, false, true, true, false, false, false, false, false]
|
||||
[false, false, false, false, true, false, false, false, false]
|
||||
[false, false, false, false, false, true, false, false, false]
|
||||
[false, false, false, false, false, false, true, false, false]
|
||||
[false, false, false, false, false, false, false, true, false]
|
||||
[false, false, false, false, false, false, false, false, true ]
|
||||
|
||||
[.[] | length]
|
||||
[[], {}, [1,2], {"a":42}, "asdf"]
|
||||
[0, 0, 2, 1, 4]
|
||||
|
||||
map(keys)
|
||||
[{}, {"abcd":1,"abc":2,"abcde":3}, {"x":1, "z": 3, "y":2}]
|
||||
[[], ["abc","abcd","abcde"], ["x","y","z"]]
|
||||
|
||||
[1,2,empty,3,empty,4]
|
||||
null
|
||||
[1,2,3,4]
|
||||
|
||||
map(add)
|
||||
[[], [1,2,3], ["a","b","c"], [[3],[4,5],[6]], [{"a":1}, {"b":2}, {"a":3}]]
|
||||
[null, 6, "abc", [3,4,5,6], {"a":3, "b": 2}]
|
||||
|
||||
#
|
||||
# User-defined functions
|
||||
# Oh god.
|
||||
#
|
||||
|
||||
def f: . + 1; def g: def g: . + 100; f | g | f; (f | g), g
|
||||
3.0
|
||||
106.0
|
||||
105.0
|
||||
|
||||
def f: (1000,2000); f
|
||||
123412345
|
||||
1000
|
||||
2000
|
||||
|
||||
([1,2] + [4,5])
|
||||
[1,2,3]
|
||||
[1,2,4,5]
|
||||
|
||||
true
|
||||
[1]
|
||||
true
|
||||
|
||||
null,1,null
|
||||
"hello"
|
||||
null
|
||||
1
|
||||
null
|
||||
|
||||
[1,2,3]
|
||||
[5,6]
|
||||
[1,2,3]
|
||||
|
||||
def f(x): x | x; f([.], . + [42])
|
||||
[1,2,3]
|
||||
[[[1,2,3]]]
|
||||
[[1,2,3],42]
|
||||
[[1,2,3,42]]
|
||||
[1,2,3,42,42]
|
||||
|
||||
# test closures and lexical scoping
|
||||
def id(x):x; 2000 as $x | def f(x):1 as $x | id([$x, x, x]); def g(x): 100 as $x | f($x,$x+x); g($x)
|
||||
"more testing"
|
||||
[1,100,2100.0,100,2100.0]
|
||||
|
||||
# test backtracking through function calls and returns
|
||||
# this test is *evil*
|
||||
[[20,10][1,0] as $x | def f: (100,200) as $y | def g: [$x + $y, .]; . + $x | g; f[0] | [f][0][1] | f]
|
||||
999999999
|
||||
[[110.0, 130.0], [210.0, 130.0], [110.0, 230.0], [210.0, 230.0], [120.0, 160.0], [220.0, 160.0], [120.0, 260.0], [220.0, 260.0]]
|
||||
|
||||
# test recursion
|
||||
def fac: if . == 1 then 1 else . * (. - 1 | fac) end; [.[] | fac]
|
||||
[1,2,3,4]
|
||||
[1,2,6,24]
|
||||
|
||||
# test stack overflow and reallocation
|
||||
# this test is disabled for now, it takes a realllllly long time.
|
||||
# def f: if length > 1000 then . else .+[1]|f end; f | length
|
||||
# []
|
||||
# 1001
|
||||
|
||||
#
|
||||
# Assignment
|
||||
#
|
||||
.message = "goodbye"
|
||||
{"message": "hello"}
|
||||
{"message": "goodbye"}
|
||||
|
||||
.foo = .bar
|
||||
{"bar":42}
|
||||
{"foo":42, "bar":42}
|
||||
|
||||
.foo |= .+1
|
||||
{"foo": 42}
|
||||
{"foo": 43}
|
||||
|
||||
.[] += 2, .[] *= 2, .[] -= 2, .[] /= 2
|
||||
[1,3,5]
|
||||
[3,5,7]
|
||||
[2,6,10]
|
||||
[-1,1,3]
|
||||
[0.5, 1.5, 2.5]
|
||||
|
||||
.[0].a |= {"old":., "new":(.+1)}
|
||||
[{"a":1,"b":2}]
|
||||
[{"a":{"old":1, "new":2},"b":2}]
|
||||
|
||||
def inc(x): x |= .+1; inc(.[].a)
|
||||
[{"a":1,"b":2},{"a":2,"b":4},{"a":7,"b":8}]
|
||||
[{"a":2,"b":2},{"a":3,"b":4},{"a":8,"b":8}]
|
||||
|
||||
.[2][3] = 1
|
||||
[4]
|
||||
[4, null, [null, null, null, 1]]
|
||||
|
||||
.foo[2].bar = 1
|
||||
{"foo":[11], "bar":42}
|
||||
{"foo":[11,null,{"bar":1}], "bar":42}
|
||||
|
||||
#
|
||||
# Conditionals
|
||||
#
|
||||
|
||||
[.[] | if .foo then "yep" else "nope" end]
|
||||
[{"foo":0},{"foo":1},{"foo":[]},{"foo":true},{"foo":false},{"foo":null},{"foo":"foo"},{}]
|
||||
["yep","yep","yep","yep","nope","nope","yep","nope"]
|
||||
|
||||
[.[] | if .baz then "strange" elif .foo then "yep" else "nope" end]
|
||||
[{"foo":0},{"foo":1},{"foo":[]},{"foo":true},{"foo":false},{"foo":null},{"foo":"foo"},{}]
|
||||
["yep","yep","yep","yep","nope","nope","yep","nope"]
|
||||
|
||||
|
||||
# FIXME: define/test behaviour of 'if (.foo,.bar) then A else B end'
|
||||
|
||||
[.[] | [.foo[] // .bar]]
|
||||
[{"foo":[1,2], "bar": 42}, {"foo":[1], "bar": null}, {"foo":[null,false,3], "bar": 18}, {"foo":[], "bar":42}, {"foo": [null,false,null], "bar": 41}]
|
||||
[[1,2], [1], [3], [42], [41]]
|
||||
|
||||
# FIXME: behaviour of update operators
|
||||
# .[] //= .[0]
|
||||
# ["hello",true,false,[false],null]
|
||||
# ["hello",true,"hello",[false],"hello"]
|
||||
|
||||
.[] | [.[0] and .[1], .[0] or .[1]]
|
||||
[[true,[]], [false,1], [42,null], [null,false]]
|
||||
[true,true]
|
||||
[false,true]
|
||||
[false,true]
|
||||
[false,false]
|
||||
|
||||
[.[] | not]
|
||||
[1,0,false,null,true,"hello"]
|
||||
[false,false,true,true,false,false]
|
||||
|
||||
# Check numeric comparison binops
|
||||
[10 > 0, 10 > 10, 10 > 20, 10 < 0, 10 < 10, 10 < 20]
|
||||
{}
|
||||
[true,false,false,false,false,true]
|
||||
|
||||
[10 >= 0, 10 >= 10, 10 >= 20, 10 <= 0, 10 <= 10, 10 <= 20]
|
||||
{}
|
||||
[true,true,false,false,true,true]
|
||||
|
||||
# And some in/equality tests
|
||||
[ 10 == 10, 10 != 10, 10 != 11, 10 == 11]
|
||||
{}
|
||||
[true,false,true,false]
|
||||
|
||||
["hello" == "hello", "hello" != "hello", "hello" == "world", "hello" != "world" ]
|
||||
{}
|
||||
[true,false,false,true]
|
||||
|
||||
[[1,2,3] == [1,2,3], [1,2,3] != [1,2,3], [1,2,3] == [4,5,6], [1,2,3] != [4,5,6]]
|
||||
{}
|
||||
[true,false,false,true]
|
||||
|
||||
[{"foo":42} == {"foo":42},{"foo":42} != {"foo":42}, {"foo":42} != {"bar":42}, {"foo":42} == {"bar":42}]
|
||||
{}
|
||||
[true,false,true,false]
|
||||
|
||||
# ugly complicated thing
|
||||
[{"foo":[1,2,{"bar":18},"world"]} == {"foo":[1,2,{"bar":18},"world"]},{"foo":[1,2,{"bar":18},"world"]} == {"foo":[1,2,{"bar":19},"world"]}]
|
||||
{}
|
||||
[true,false]
|
||||
|
||||
# containment operator
|
||||
[("foo" | contains("foo")), ("foobar" | contains("foo")), ("foo" | contains("foobar"))]
|
||||
{}
|
||||
[true, true, false]
|
||||
|
||||
map(.[1] as $needle | .[0] | contains($needle))
|
||||
[[[],[]], [[1,2,3], [1,2]], [[1,2,3], [3,1]], [[1,2,3], [4]], [[1,2,3], [1,4]]]
|
||||
[true, true, true, false, false]
|
||||
|
||||
map(.[1] as $needle | .[0] | contains($needle))
|
||||
[[["foobar", "foobaz"], ["baz", "bar"]], [["foobar", "foobaz"], ["foo"]], [["foobar", "foobaz"], ["blap"]]]
|
||||
[true, true, false]
|
||||
|
||||
[({foo: 12, bar:13} | contains({foo: 12})), ({foo: 12} | contains({})), ({foo: 12, bar:13} | contains({baz:14}))]
|
||||
{}
|
||||
[true, true, false]
|
||||
|
||||
{foo: {baz: 12, blap: {bar: 13}}, bar: 14} | contains({bar: 14, foo: {blap: {}}})
|
||||
{}
|
||||
true
|
||||
|
||||
{foo: {baz: 12, blap: {bar: 13}}, bar: 14} | contains({bar: 14, foo: {blap: {bar: 14}}})
|
||||
{}
|
||||
false
|
||||
|
||||
sort
|
||||
[42,[2,5,3,11],10,{"a":42,"b":2},{"a":42},true,2,[2,6],"hello",null,[2,5,6],{"a":[],"b":1},"abc","ab",[3,10],{},false,"abcd",null]
|
||||
[null,null,false,true,2,10,42,"ab","abc","abcd","hello",[2,5,3,11],[2,5,6],[2,6],[3,10],{},{"a":42},{"a":42,"b":2},{"a":[],"b":1}]
|
||||
|
||||
(sort_by(.b) | sort_by(.a)), sort_by(.a, .b), sort_by(.b, .c), group_by(.b), group_by(.a + .b - .c == 2)
|
||||
[{"a": 1, "b": 4, "c": 14}, {"a": 4, "b": 1, "c": 3}, {"a": 1, "b": 4, "c": 3}, {"a": 0, "b": 2, "c": 43}]
|
||||
[{"a": 0, "b": 2, "c": 43}, {"a": 1, "b": 4, "c": 14}, {"a": 1, "b": 4, "c": 3}, {"a": 4, "b": 1, "c": 3}]
|
||||
[{"a": 0, "b": 2, "c": 43}, {"a": 1, "b": 4, "c": 14}, {"a": 1, "b": 4, "c": 3}, {"a": 4, "b": 1, "c": 3}]
|
||||
[{"a": 4, "b": 1, "c": 3}, {"a": 0, "b": 2, "c": 43}, {"a": 1, "b": 4, "c": 3}, {"a": 1, "b": 4, "c": 14}]
|
||||
[[{"a": 4, "b": 1, "c": 3}], [{"a": 0, "b": 2, "c": 43}], [{"a": 1, "b": 4, "c": 14}, {"a": 1, "b": 4, "c": 3}]]
|
||||
[[{"a": 1, "b": 4, "c": 14}, {"a": 0, "b": 2, "c": 43}], [{"a": 4, "b": 1, "c": 3}, {"a": 1, "b": 4, "c": 3}]]
|
||||
|
||||
unique
|
||||
[1,2,5,3,5,3,1,3]
|
||||
[1,2,3,5]
|
||||
|
||||
[min, max, min_by(.[1]), max_by(.[1]), min_by(.[2]), max_by(.[2])]
|
||||
[[4,2,"a"],[3,1,"a"],[2,4,"a"],[1,3,"a"]]
|
||||
[[1,3,"a"],[4,2,"a"],[3,1,"a"],[2,4,"a"],[4,2,"a"],[1,3,"a"]]
|
||||
|
||||
[min,max,min_by(.),max_by(.)]
|
||||
[]
|
||||
[null,null,null,null]
|
||||
|
||||
.foo[.baz]
|
||||
{"foo":{"bar":4},"baz":"bar"}
|
||||
4
|
||||
Reference in new issue
Block a user