Index: Makefile.in ================================================================== --- Makefile.in +++ Makefile.in @@ -7,10 +7,16 @@ #### The toplevel directory of the source tree. Fossil can be built # in a directory that is separate from the source tree. Just change # the following to point from the build directory to the src/ folder. # SRCDIR = @srcdir@/src +#### Upstream source files included directly in this repository. +# +SRCDIR_extsrc = @srcdir@/extsrc +#### In-tree tools such as code generators and translators: +# +SRCDIR_tools = @srcdir@/tools #### The directory into which object code files should be written. # Having a "./" prefix in the value of this variable breaks our use of the # "makeheaders" tool when running make on the MinGW platform, apparently # due to some command line argument manipulation performed automatically DELETED ajax/README Index: ajax/README ================================================================== --- ajax/README +++ /dev/null @@ -1,38 +0,0 @@ -This is the README for how to set up the Fossil/JSON test web page -under Apache on Unix systems. This is only intended only for -Fossil/JSON developers/tinkerers: - -First, copy cgi-bin/fossil-json.cgi.example to -cgi-bin/fossil-json.cgi. Edit it and correct the paths to the fossil -binary and the repo you want to serve. Make it executable. - -MAKE SURE that the fossil repo you use is world-writable OR that your -Web/CGI server is set up to run as the user ID of the owner of the -fossil file. ALSO: the DIRECTORY CONTAINING the repo file must be -writable by the CGI process. - -Next, set up an apache vhost entry. Mine looks like: - - - ServerAlias fjson - ScriptAlias /cgi-bin/ /home/stephan/cvs/fossil/fossil-json/ajax/cgi-bin/ - DocumentRoot /home/stephan/cvs/fossil/fossil-json/ajax - - -Now add your preferred vhost name (fjson in the above example) to /etc/hosts: - - 127.0.0.1 ...other aliases... fjson - -Restart your Apache. - -Now visit: http://fjson/ - -that will show the test/demo page. If it doesn't, edit index.html and -make sure that: - - WhAjaj.Connector.options.ajax.url = ...; - -points to your CGI script. In theory you can also do this over fossil -standalone server mode, but i haven't yet tested that particular test -page in that mode. - DELETED ajax/cgi-bin/fossil-json.cgi.example Index: ajax/cgi-bin/fossil-json.cgi.example ================================================================== --- ajax/cgi-bin/fossil-json.cgi.example +++ /dev/null @@ -1,2 +0,0 @@ -#!/path/to/fossil/binary -repository: /path/to/repo.fsl DELETED ajax/i-test/rhino-shell.js Index: ajax/i-test/rhino-shell.js ================================================================== --- ajax/i-test/rhino-shell.js +++ /dev/null @@ -1,208 +0,0 @@ -var FShell = { - serverUrl: - 'http://localhost:8080' - //'http://fjson/cgi-bin/fossil-json.cgi' - //'http://192.168.1.62:8080' - //'http://fossil.wanderinghorse.net/repos/fossil-json-java/index.cgi' - , - verbose:false, - prompt:"fossil shell > ", - wiki:{}, - consol:java.lang.System.console(), - v:function(msg){ - if(this.verbose){ - print("VERBOSE: "+msg); - } - } -}; -(function bootstrap() { - var srcdir = '../js/'; - var includes = [srcdir+'json2.js', - srcdir+'whajaj.js', - srcdir+'fossil-ajaj.js' - ]; - for( var i in includes ) { - load(includes[i]); - } - WhAjaj.Connector.prototype.sendImpl = WhAjaj.Connector.sendImpls.rhino; - FShell.fossil = new FossilAjaj({ - asynchronous:false, /* rhino-based impl doesn't support async. */ - timeout:10000, - url:FShell.serverUrl - }); - print("Server: "+FShell.serverUrl); - var cb = FShell.fossil.ajaj.callbacks; - cb.beforeSend = function(req,opt){ - if(!FShell.verbose) return; - print("SENDING REQUEST: AJAJ options="+JSON.stringify(opt)); - if(req) print("Request envelope="+WhAjaj.stringify(req)); - }; - cb.afterSend = function(req,opt){ - //if(!FShell.verbose) return; - //print("REQUEST RETURNED: opt="+JSON.stringify(opt)); - //if(req) print("Request="+WhAjaj.stringify(req)); - }; - cb.onError = function(req,opt){ - //if(!FShell.verbose) return; - print("ERROR: "+WhAjaj.stringify(opt)); - }; - cb.onResponse = function(resp,req){ - if(!FShell.verbose) return; - if(resp && resp.resultCode){ - print("Response contains error info: "+resp.resultCode+": "+resp.resultText); - } - print("GOT RESPONSE: "+(('string'===typeof resp) ? resp : WhAjaj.stringify(resp))); - }; - FShell.fossil.HAI({ - onResponse:function(resp,opt){ - assertResponseOK(resp); - } - }); -})(); - -/** - Throws an exception of cond is a falsy value. -*/ -function assert(cond, descr){ - descr = descr || "Undescribed condition."; - if(!cond){ - throw new Error("Assertion failed: "+descr); - }else{ - //print("Assertion OK: "+descr); - } -} - -/** - Convenience form of FShell.fossil.sendCommand(command,payload,ajajOpt). -*/ -function send(command,payload, ajajOpt){ - FShell.fossil.sendCommand(command,payload,ajajOpt); -} - -/** - Asserts that resp is-a Object, resp.fossil is-a string, and - !resp.resultCode. -*/ -function assertResponseOK(resp){ - assert('object' === typeof resp,'Response is-a object.'); - assert( 'string' === typeof resp.fossil, 'Response contains fossil property.'); - assert( !resp.resultCode, 'resp.resultCode='+resp.resultCode); -} -/** - Asserts that resp is-a Object, resp.fossil is-a string, and - resp.resultCode is a truthy value. If expectCode is set then - it also asserts that (resp.resultCode=='FOSSIL-'+expectCode). -*/ -function assertResponseError(resp,expectCode){ - assert('object' === typeof resp,'Response is-a object.'); - assert( 'string' === typeof resp.fossil, 'Response contains fossil property.'); - assert( resp.resultCode, 'resp.resultCode='+resp.resultCode); - if(expectCode){ - assert( 'FOSSIL-'+expectCode == resp.resultCode, 'Expecting result code '+expectCode ); - } -} - -FShell.readline = (typeof readline === 'function') ? (readline) : (function() { - importPackage(java.io); - importPackage(java.lang); - var stdin = new BufferedReader(new InputStreamReader(System['in'])); - var self = this; - return function(prompt) { - if(prompt) print(prompt); - var x = stdin.readLine(); - return null===x ? x : String(x) /*convert to JS string!*/; - }; -}()); - -FShell.dispatchLine = function(line){ - var av = line.split(' '); // FIXME: to shell-like tokenization. Too tired! - var cmd = av[0]; - var key, h; - if('/' == cmd[0]) key = '/'; - else key = this.commandAliases[cmd]; - if(!key) key = cmd; - h = this.commandHandlers[key]; - if(!h){ - print("Command not known: "+cmd +" ("+key+")"); - }else if(!WhAjaj.isFunction(h)){ - print("Not a function: "+key); - } - else{ - print("Sending ["+key+"] command... "); - try{h(av);} - catch(e){ print("EXCEPTION: "+e); } - } -}; - -FShell.onResponseDefault = function(callback){ - return function(resp,req){ - assertResponseOK(resp); - print("Payload: "+(resp.payload ? WhAjaj.stringify(resp.payload) : "none")); - if(WhAjaj.isFunction(callback)){ - callback(resp,req); - } - }; -}; -FShell.commandHandlers = { - "?":function(args){ - var k; - print("Available commands...\n"); - var o = FShell.commandHandlers; - for(k in o){ - if(! o.hasOwnProperty(k)) continue; - print("\t"+k); - } - }, - "/":function(args){ - FShell.fossil.sendCommand('/json'+args[0],undefined,{ - beforeSend:function(req,opt){ - print("Sending to: "+opt.url); - }, - onResponse:FShell.onResponseDefault() - }); - }, - "eval":function(args){ - eval(args.join(' ')); - }, - "login":function(args){ - FShell.fossil.login(args[1], args[2], { - onResponse:FShell.onResponseDefault() - }); - }, - "whoami":function(args){ - FShell.fossil.whoami({ - onResponse:FShell.onResponseDefault() - }); - }, - "HAI":function(args){ - FShell.fossil.HAI({ - onResponse:FShell.onResponseDefault() - }); - } - -}; -FShell.commandAliases = { - "li":"login", - "lo":"logout", - "who":"whoami", - "hi":"HAI", - "tci":"/timeline/ci?limit=3" -}; -FShell.mainLoop = function(){ - var line; - var check = /\S/; - //var isJavaNull = /java\.lang\.null/; - //print(typeof java.lang['null']); - while( null != (line=this.readline(this.prompt)) ){ - if(null===line) break /*EOF*/; - else if( "" === line ) continue; - //print("Got line: "+line); - else if(!check.test(line)) continue; - print('typeof line = '+typeof line); - this.dispatchLine(line); - print(""); - } - print("Bye!"); -}; - -FShell.mainLoop(); DELETED ajax/i-test/rhino-test.js Index: ajax/i-test/rhino-test.js ================================================================== --- ajax/i-test/rhino-test.js +++ /dev/null @@ -1,279 +0,0 @@ -var TestApp = { - serverUrl: - 'http://localhost:8080' - //'http://fjson/cgi-bin/fossil-json.cgi' - //'http://192.168.1.62:8080' - //'http://fossil.wanderinghorse.net/repos/fossil-json-java/index.cgi' - , - verbose:true, - fossilBinary:'fossil', - wiki:{} -}; -(function bootstrap() { - var srcdir = '../js/'; - var includes = [srcdir+'json2.js', - srcdir+'whajaj.js', - srcdir+'fossil-ajaj.js' - ]; - for( var i in includes ) { - load(includes[i]); - } - WhAjaj.Connector.prototype.sendImpl = WhAjaj.Connector.sendImpls.rhino; - TestApp.fossil = new FossilAjaj({ - asynchronous:false, /* rhino-based impl doesn't support async or timeout. */ - timeout:0, - url:TestApp.serverUrl, - fossilBinary:TestApp.fossilBinary - }); - var cb = TestApp.fossil.ajaj.callbacks; - cb.beforeSend = function(req,opt){ - if(!TestApp.verbose) return; - print("SENDING REQUEST: AJAJ options="+JSON.stringify(opt)); - if(req) print("Request envelope="+WhAjaj.stringify(req)); - }; - cb.afterSend = function(req,opt){ - //if(!TestApp.verbose) return; - //print("REQUEST RETURNED: opt="+JSON.stringify(opt)); - //if(req) print("Request="+WhAjaj.stringify(req)); - }; - cb.onError = function(req,opt){ - if(!TestApp.verbose) return; - print("ERROR: "+WhAjaj.stringify(opt)); - }; - cb.onResponse = function(resp,req){ - if(!TestApp.verbose) return; - print("GOT RESPONSE: "+(('string'===typeof resp) ? resp : WhAjaj.stringify(resp))); - }; - -})(); - -/** - Throws an exception of cond is a falsy value. -*/ -function assert(cond, descr){ - descr = descr || "Undescribed condition."; - if(!cond){ - print("Assertion FAILED: "+descr); - throw new Error("Assertion failed: "+descr); - // aarrgghh. Exceptions are of course swallowed by - // the AJAX layer, to keep from killing a browser's - // script environment. - }else{ - if(TestApp.verbose) print("Assertion OK: "+descr); - } -} - -/** - Calls func() in a try/catch block and throws an exception if - func() does NOT throw. -*/ -function assertThrows(func, descr){ - descr = descr || "Undescribed condition failed."; - var ex; - try{ - func(); - }catch(e){ - ex = e; - } - if(!ex){ - throw new Error("Function did not throw (as expected): "+descr); - }else{ - if(TestApp.verbose) print("Function threw (as expected): "+descr+": "+ex); - } -} - -/** - Convenience form of TestApp.fossil.sendCommand(command,payload,ajajOpt). -*/ -function send(command,payload, ajajOpt){ - TestApp.fossil.sendCommand(command,payload,ajajOpt); -} - -/** - Asserts that resp is-a Object, resp.fossil is-a string, and - !resp.resultCode. -*/ -function assertResponseOK(resp){ - assert('object' === typeof resp,'Response is-a object.'); - assert( 'string' === typeof resp.fossil, 'Response contains fossil property.'); - assert( undefined === resp.resultCode, 'resp.resultCode is not set'); -} -/** - Asserts that resp is-a Object, resp.fossil is-a string, and - resp.resultCode is a truthy value. If expectCode is set then - it also asserts that (resp.resultCode=='FOSSIL-'+expectCode). -*/ -function assertResponseError(resp,expectCode){ - assert('object' === typeof resp,'Response is-a object.'); - assert( 'string' === typeof resp.fossil, 'Response contains fossil property.'); - assert( !!resp.resultCode, 'resp.resultCode='+resp.resultCode); - if(expectCode){ - assert( 'FOSSIL-'+expectCode == resp.resultCode, 'Expecting result code '+expectCode ); - } -} - -function testHAI(){ - var rs; - TestApp.fossil.HAI({ - onResponse:function(resp,req){ - rs = resp; - } - }); - assertResponseOK(rs); - TestApp.serverVersion = rs.fossil; - assert( 'string' === typeof TestApp.serverVersion, 'server version = '+TestApp.serverVersion); -} -testHAI.description = 'Get server version info.'; - -function testIAmNobody(){ - TestApp.fossil.whoami('/json/whoami'); - assert('nobody' === TestApp.fossil.auth.name, 'User == nobody.' ); - assert(!TestApp.fossil.auth.authToken, 'authToken is not set.' ); - -} -testIAmNobody.description = 'Ensure that current user is "nobody".'; - - -function testAnonymousLogin(){ - TestApp.fossil.login(); - assert('string' === typeof TestApp.fossil.auth.authToken, 'authToken = '+TestApp.fossil.auth.authToken); - assert( 'string' === typeof TestApp.fossil.auth.name, 'User name = '+TestApp.fossil.auth.name); - TestApp.fossil.userName = null; - TestApp.fossil.whoami('/json/whoami'); - assert( 'string' === typeof TestApp.fossil.auth.name, 'User name = '+TestApp.fossil.auth.name); -} -testAnonymousLogin.description = 'Perform anonymous login.'; - -function testAnonWiki(){ - var rs; - TestApp.fossil.sendCommand('/json/wiki/list',undefined,{ - beforeSend:function(req,opt){ - assert( req && (req.authToken==TestApp.fossil.auth.authToken), 'Request envelope contains expected authToken.' ); - }, - onResponse:function(resp,req){ - rs = resp; - } - }); - assertResponseOK(rs); - assert( (typeof [] === typeof rs.payload) && rs.payload.length, - "Wiki list seems to be okay."); - TestApp.wiki.list = rs.payload; - - TestApp.fossil.sendCommand('/json/wiki/get',{ - name:TestApp.wiki.list[0] - },{ - onResponse:function(resp,req){ - rs = resp; - } - }); - assertResponseOK(rs); - assert(rs.payload.name == TestApp.wiki.list[0], "Fetched page name matches expectations."); - print("Got first wiki page: "+WhAjaj.stringify(rs.payload)); - -} -testAnonWiki.description = 'Fetch wiki list as anonymous user.'; - -function testFetchCheckinArtifact(){ - var art = '18dd383e5e7684ece'; - var rs; - TestApp.fossil.sendCommand('/json/artifact',{ - 'name': art - }, - { - onResponse:function(resp,req){ - rs = resp; - } - }); - assertResponseOK(rs); - assert(3 == rs.payload.parents.length, 'Got 3 parent artifacts.'); -} -testFetchCheckinArtifact.description = '/json/artifact/CHECKIN'; - -function testAnonLogout(){ - var rs; - TestApp.fossil.logout({ - onResponse:function(resp,req){ - rs = resp; - } - }); - assertResponseOK(rs); - print("Ensure that second logout attempt fails..."); - TestApp.fossil.logout({ - onResponse:function(resp,req){ - rs = resp; - } - }); - assertResponseError(rs); -} -testAnonLogout.description = 'Log out anonymous user.'; - -function testExternalProcess(){ - - var req = { command:"HAI", requestId:'testExternalProcess()' }; - var args = [TestApp.fossilBinary, 'json', '--json-input', '-']; - var p = java.lang.Runtime.getRuntime().exec(args); - var outs = p.getOutputStream(); - var osr = new java.io.OutputStreamWriter(outs); - var osb = new java.io.BufferedWriter(osr); - var json = JSON.stringify(req); - osb.write(json,0, json.length); - osb.close(); - req = json = outs = osr = osb = undefined; - var ins = p.getInputStream(); - var isr = new java.io.InputStreamReader(ins); - var br = new java.io.BufferedReader(isr); - var line; - - while( null !== (line=br.readLine())){ - print(line); - } - br.close(); - isr.close(); - ins.close(); - p.waitFor(); -} -testExternalProcess.description = 'Run fossil as external process.'; - -function testExternalProcessHandler(){ - var aj = TestApp.fossil.ajaj; - var oldImpl = aj.sendImpl; - aj.sendImpl = FossilAjaj.rhinoLocalBinarySendImpl; - var rs; - TestApp.fossil.sendCommand('/json/HAI',undefined,{ - onResponse:function(resp,opt){ - rs = resp; - } - }); - aj.sendImpl = oldImpl; - assertResponseOK(rs); - print("Using local fossil binary via AJAX interface, we fetched: "+ - WhAjaj.stringify(rs)); -} -testExternalProcessHandler.description = 'Try local fossil binary via AJAX interface.'; - -(function runAllTests(){ - var testList = [ - testHAI, - testIAmNobody, - testAnonymousLogin, - testAnonWiki, - testFetchCheckinArtifact, - testAnonLogout, - testExternalProcess, - testExternalProcessHandler - ]; - var i, f; - for( i = 0; i < testList.length; ++i ){ - f = testList[i]; - try{ - print("Running test #"+(i+1)+": "+(f.description || "no description.")); - f(); - }catch(e){ - print("Test #"+(i+1)+" failed: "+e); - throw e; - } - } - -})(); - -print("Done! If you don't see an exception message in the last few lines, you win!"); DELETED ajax/index.html Index: ajax/index.html ================================================================== --- ajax/index.html +++ /dev/null @@ -1,332 +0,0 @@ - - - - - Fossil/JSON raw request sending - - - - - - - - - - - - - -
-

You know, for sending raw JSON requests to Fossil...

- -If you're actually using this page, then you know what you're doing and don't -need help text, hoverhelp, and a snazzy interface. - -

- - -JSON API docs: https://docs.google.com/document/d/1fXViveNhDbiXgCuE7QDXQOKeFzf2qNUkBEgiUvoqFN4/edit - -
-See also: prototype wiki editor. - -

Request...

- -Path: -
-If the POST textarea is not empty then it will be posted with the request. -
-Quick-posts:
- - - - - - - - - -
- - - - - - - -
- - - - - - - -
- - - - - - - - - -
- - - - - - - - - - - - - - - - -
-Login: -
- - -
-name: -pw: - - -
- - -
- -
- - - - - - - - - - - - - - - - -
POST dataRequest AJAJ options
- - - -
Response
- -
-
-
-
- - DELETED ajax/js/fossil-ajaj.js Index: ajax/js/fossil-ajaj.js ================================================================== --- ajax/js/fossil-ajaj.js +++ /dev/null @@ -1,274 +0,0 @@ -/** - This file contains a WhAjaj extension for use with Fossil/JSON. - - Author: Stephan Beal (sgbeal@googlemail.com) - - License: Public Domain -*/ - -/** - Constructor for a new Fossil AJAJ client. ajajOpt may be an optional - object suitable for passing to the WhAjaj.Connector() constructor. - - On returning, this.ajaj is-a WhAjaj.Connector instance which can - be used to send requests to the back-end (though the convenience - functions of this class are the preferred way to do it). Clients - are encouraged to use FossilAjaj.sendCommand() (and friends) instead - of the underlying WhAjaj.Connector API, since this class' API - contains Fossil-specific request-calling handling (e.g. of authentication - info) whereas WhAjaj is more generic. -*/ -function FossilAjaj(ajajOpt) -{ - this.ajaj = new WhAjaj.Connector(ajajOpt); - return this; -} - -FossilAjaj.prototype.generateRequestId = function() { - return this.ajaj.generateRequestId(); -}; - -/** - Proxy for this.ajaj.sendRequest(). -*/ -FossilAjaj.prototype.sendRequest = function(req,opt) { - return this.ajaj.sendRequest(req,opt); -}; - -/** - Sends a command to the fossil back-end. Command should be the - path part of the URL, e.g. /json/stat, payload is a request-specific - value type (may often be null/undefined). ajajOpt is an optional object - holding WhAjaj.sendRequest()-compatible options. - - This function constructs a Fossil/JSON request envelope based - on the given arguments and adds this.auth.authToken and a requestId - to it. -*/ -FossilAjaj.prototype.sendCommand = function(command, payload, ajajOpt) { - var req; - ajajOpt = ajajOpt || {}; - if(payload || (this.auth && this.auth.authToken) || ajajOpt.jsonp) { - req = { - payload:payload, - requestId:('function' === typeof this.generateRequestId) ? this.generateRequestId() : undefined, - authToken:(this.auth ? this.auth.authToken : undefined), - jsonp:('string' === typeof ajajOpt.jsonp) ? ajajOpt.jsonp : undefined - }; - } - ajajOpt.method = req ? 'POST' : 'GET'; - // just for debuggering: ajajOpt.method = 'POST'; if(!req) req={}; - if(command) ajajOpt.url = this.ajaj.derivedOption('url',ajajOpt) + command; - this.ajaj.sendRequest(req,ajajOpt); -}; - -/** - Sends a login request to the back-end. - - ajajOpt is an optional configuration object suitable for passing - to sendCommand(). - - After the response returns, this.auth will be - set to the response payload. - - If name === 'anonymous' (the default if none is passed in) then this - function ignores the pw argument and must make two requests - the first - one gets the captcha code and the second one submits it. - ajajOpt.onResponse() (if set) is only called for the actual login - response (the 2nd one), as opposed to being called for both requests. - However, this.ajaj.callbacks.onResponse() _is_ called for both (because - it happens at a lower level). - - If this object has an onLogin() function it is called (with - no arguments) before the onResponse() handler of the login is called - (that is the 2nd request for anonymous logins) and any exceptions - it throws are ignored. - -*/ -FossilAjaj.prototype.login = function(name,pw,ajajOpt) { - name = name || 'anonymous'; - var self = this; - var loginReq = { - name:name, - password:pw - }; - ajajOpt = this.ajaj.normalizeAjaxParameters( ajajOpt || {} ); - var oldOnResponse = ajajOpt.onResponse; - ajajOpt.onResponse = function(resp,req) { - var thisOpt = this; - //alert('login response:\n'+WhAjaj.stringify(resp)); - if( resp && resp.payload ) { - //self.userName = resp.payload.name; - //self.capabilities = resp.payload.capabilities; - self.auth = resp.payload; - } - if( WhAjaj.isFunction( self.onLogin ) ){ - try{ self.onLogin(); } - catch(e){} - } - if( WhAjaj.isFunction(oldOnResponse) ) { - oldOnResponse.apply(thisOpt,[resp,req]); - } - }; - function doLogin(){ - //alert("Sending login request..."+WhAjaj.stringify(loginReq)); - self.sendCommand('/json/login', loginReq, ajajOpt); - } - if( 'anonymous' === name ){ - this.sendCommand('/json/anonymousPassword',undefined,{ - onResponse:function(resp,req){ -/* - if( WhAjaj.isFunction(oldOnResponse) ){ - oldOnResponse.apply(this, [resp,req]); - }; -*/ - if(resp && !resp.resultCode){ - //alert("Got PW. Trying to log in..."+WhAjaj.stringify(resp)); - loginReq.anonymousSeed = resp.payload.seed; - loginReq.password = resp.payload.password; - doLogin(); - } - } - }); - } - else doLogin(); -}; - -/** - Logs out of fossil, invaliding this login token. - - ajajOpt is an optional configuration object suitable for passing - to sendCommand(). - - If this object has an onLogout() function it is called (with - no arguments) before the onResponse() handler is called. - IFF the response succeeds then this.auth is unset. -*/ -FossilAjaj.prototype.logout = function(ajajOpt) { - var self = this; - ajajOpt = this.ajaj.normalizeAjaxParameters( ajajOpt || {} ); - var oldOnResponse = ajajOpt.onResponse; - ajajOpt.onResponse = function(resp,req) { - var thisOpt = this; - self.auth = undefined; - if( WhAjaj.isFunction( self.onLogout ) ){ - try{ self.onLogout(); } - catch(e){} - } - if( WhAjaj.isFunction(oldOnResponse) ) { - oldOnResponse.apply(thisOpt,[resp,req]); - } - }; - this.sendCommand('/json/logout', undefined, ajajOpt ); -}; - -/** - Sends a HAI request to the server. /json/HAI is an alias /json/version. - - ajajOpt is an optional configuration object suitable for passing - to sendCommand(). -*/ -FossilAjaj.prototype.HAI = function(ajajOpt) { - this.sendCommand('/json/HAI', undefined, ajajOpt); -}; - - -/** - Sends a /json/whoami request. Updates this.auth to contain - the login info, removing them if the response does not contain - that data. -*/ -FossilAjaj.prototype.whoami = function(ajajOpt) { - var self = this; - ajajOpt = this.ajaj.normalizeAjaxParameters( ajajOpt || {} ); - var oldOnResponse = ajajOpt.onResponse; - ajajOpt.onResponse = function(resp,req) { - var thisOpt = this; - if( resp && resp.payload ){ - if(!self.auth || (self.auth.authToken!==resp.payload.authToken)){ - self.auth = resp.payload; - if( WhAjaj.isFunction(self.onLogin) ){ - self.onLogin(); - } - } - } - else { delete self.auth; } - if( WhAjaj.isFunction(oldOnResponse) ) { - oldOnResponse.apply(thisOpt,[resp,req]); - } - }; - self.sendCommand('/json/whoami', undefined, ajajOpt); -}; - -/** - EXPERIMENTAL concrete WhAjaj.Connector.sendImpl() implementation which - uses Rhino to connect to a local fossil binary for input and output. Its - signature and semantics are as described for - WhAjaj.Connector.prototype.sendImpl(), with a few exceptions and - additions: - - - It does not support timeouts or asynchronous mode. - - - The args.fossilBinary property must point to the local fossil binary - (it need not be a complete path if fossil is in the $PATH). This - function throws (without calling any request callbacks) if - args.fossilBinary is not set. fossilBinary may be set on - WhAjaj.Connector.options.ajax, in the FossilAjaj constructor call, as - the ajax options parameter to any of the FossilAjaj.sendCommand() family - of functions, or by setting - aFossilAjajInstance.ajaj.options.fossilBinary on a specific - FossilAjaj instance. - - - It uses the args.url field to create the "command" property of the - request, constructs a request envelope, spawns a fossil process in JSON - mode, feeds it the request envelope, and returns the response envelope - via the same mechanisms defined for the HTTP-based implementations. - - The interface is otherwise compatible with the "normal" - FossilAjaj.sendCommand() front-end (it is, however, fossil-specific, and - not back-end agnostic like the WhAjaj.sendImpl() interface intends). - - -*/ -FossilAjaj.rhinoLocalBinarySendImpl = function(request,args){ - var self = this; - request = request || {}; - if(!args.fossilBinary){ - throw new Error("fossilBinary is not set on AJAX options!"); - } - var url = args.url.split('?')[0].split(/\/+/); - if(url.length>1){ - // 3x shift(): protocol, host, 'json' part of path - request.command = (url.shift(),url.shift(),url.shift(), url.join('/')); - } - delete args.url; - //print("rhinoLocalBinarySendImpl SENDING: "+WhAjaj.stringify(request)); - var json; - try{ - var pargs = [args.fossilBinary, 'json', '--json-input', '-']; - var p = java.lang.Runtime.getRuntime().exec(pargs); - var outs = p.getOutputStream(); - var osr = new java.io.OutputStreamWriter(outs); - var osb = new java.io.BufferedWriter(osr); - - json = JSON.stringify(request); - osb.write(json,0, json.length); - osb.close(); - var ins = p.getInputStream(); - var isr = new java.io.InputStreamReader(ins); - var br = new java.io.BufferedReader(isr); - var line; - json = []; - while( null !== (line=br.readLine())){ - json.push(line); - } - ins.close(); - }catch(e){ - args.errorMessage = e.toString(); - WhAjaj.Connector.sendHelper.onSendError.apply( self, [request, args] ); - return undefined; - } - json = json.join(''); - //print("READ IN JSON: "+json); - WhAjaj.Connector.sendHelper.onSendSuccess.apply( self, [request, json, args] ); -}/*rhinoLocalBinary*/ DELETED ajax/js/json2.js Index: ajax/js/json2.js ================================================================== --- ajax/js/json2.js +++ /dev/null @@ -1,476 +0,0 @@ -/* - http://www.JSON.org/json2.js - 2009-06-29 - - Public Domain. - - NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK. - - See http://www.JSON.org/js.html - - This file creates a global JSON object containing two methods: stringify - and parse. - - JSON.stringify(value, replacer, space) - value any JavaScript value, usually an object or array. - - replacer an optional parameter that determines how object - values are stringified for objects. It can be a - function or an array of strings. - - space an optional parameter that specifies the indentation - of nested structures. If it is omitted, the text will - be packed without extra whitespace. If it is a number, - it will specify the number of spaces to indent at each - level. If it is a string (such as '\t' or ' '), - it contains the characters used to indent at each level. - - This method produces a JSON text from a JavaScript value. - - When an object value is found, if the object contains a toJSON - method, its toJSON method will be called and the result will be - stringified. A toJSON method does not serialize: it returns the - value represented by the name/value pair that should be serialized, - or undefined if nothing should be serialized. The toJSON method - will be passed the key associated with the value, and this will be - bound to the object holding the key. - - For example, this would serialize Dates as ISO strings. - - Date.prototype.toJSON = function (key) { - function f(n) { - // Format integers to have at least two digits. - return n < 10 ? '0' + n : n; - } - - return this.getUTCFullYear() + '-' + - f(this.getUTCMonth() + 1) + '-' + - f(this.getUTCDate()) + 'T' + - f(this.getUTCHours()) + ':' + - f(this.getUTCMinutes()) + ':' + - f(this.getUTCSeconds()) + 'Z'; - }; - - You can provide an optional replacer method. It will be passed the - key and value of each member, with this bound to the containing - object. The value that is returned from your method will be - serialized. If your method returns undefined, then the member will - be excluded from the serialization. - - If the replacer parameter is an array of strings, then it will be - used to select the members to be serialized. It filters the results - such that only members with keys listed in the replacer array are - stringified. - - Values that do not have JSON representations, such as undefined or - functions, will not be serialized. Such values in objects will be - dropped; in arrays they will be replaced with null. You can use - a replacer function to replace those with JSON values. - JSON.stringify(undefined) returns undefined. - - The optional space parameter produces a stringification of the - value that is filled with line breaks and indentation to make it - easier to read. - - If the space parameter is a non-empty string, then that string will - be used for indentation. If the space parameter is a number, then - the indentation will be that many spaces. - - Example: - - text = JSON.stringify(['e', {pluribus: 'unum'}]); - // text is '["e",{"pluribus":"unum"}]' - - - text = JSON.stringify(['e', {pluribus: 'unum'}], null, '\t'); - // text is '[\n\t"e",\n\t{\n\t\t"pluribus": "unum"\n\t}\n]' - - text = JSON.stringify([new Date()], function (key, value) { - return this[key] instanceof Date ? - 'Date(' + this[key] + ')' : value; - }); - // text is '["Date(---current time---)"]' - - - JSON.parse(text, reviver) - This method parses a JSON text to produce an object or array. - It can throw a SyntaxError exception. - - The optional reviver parameter is a function that can filter and - transform the results. It receives each of the keys and values, - and its return value is used instead of the original value. - If it returns what it received, then the structure is not modified. - If it returns undefined then the member is deleted. - - Example: - - // Parse the text. Values that look like ISO date strings will - // be converted to Date objects. - - myData = JSON.parse(text, function (key, value) { - var a; - if (typeof value === 'string') { - a = -/^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}(?:\.\d*)?)Z$/.exec(value); - if (a) { - return new Date(Date.UTC(+a[1], +a[2] - 1, +a[3], +a[4], - +a[5], +a[6])); - } - } - return value; - }); - - myData = JSON.parse('["Date(09/09/2001)"]', function (key, value) { - var d; - if (typeof value === 'string' && - value.slice(0, 5) === 'Date(' && - value.slice(-1) === ')') { - d = new Date(value.slice(5, -1)); - if (d) { - return d; - } - } - return value; - }); - - - This is a reference implementation. You are free to copy, modify, or - redistribute. - - This code should be minified before deployment. - See http://javascript.crockford.com/jsmin.html - - USE YOUR OWN COPY. IT IS EXTREMELY UNWISE TO LOAD CODE FROM SERVERS YOU DO - NOT CONTROL. -*/ - -/*jslint evil: true */ - -/*members "", "\b", "\t", "\n", "\f", "\r", "\"", JSON, "\\", apply, - call, charCodeAt, getUTCDate, getUTCFullYear, getUTCHours, - getUTCMinutes, getUTCMonth, getUTCSeconds, hasOwnProperty, join, - lastIndex, length, parse, prototype, push, replace, slice, stringify, - test, toJSON, toString, valueOf -*/ - -// Create a JSON object only if one does not already exist. We create the -// methods in a closure to avoid creating global variables. - -var JSON = JSON || {}; - -(function () { - - function f(n) { - // Format integers to have at least two digits. - return n < 10 ? '0' + n : n; - } - - if (typeof Date.prototype.toJSON !== 'function') { - - Date.prototype.toJSON = function (key) { - - return isFinite(this.valueOf()) ? - this.getUTCFullYear() + '-' + - f(this.getUTCMonth() + 1) + '-' + - f(this.getUTCDate()) + 'T' + - f(this.getUTCHours()) + ':' + - f(this.getUTCMinutes()) + ':' + - f(this.getUTCSeconds()) + 'Z' : null; - }; - - String.prototype.toJSON = - Number.prototype.toJSON = - Boolean.prototype.toJSON = function (key) { - return this.valueOf(); - }; - } - - var cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g, - escapable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g, - gap, - indent, - meta = { // table of character substitutions - '\b': '\\b', - '\t': '\\t', - '\n': '\\n', - '\f': '\\f', - '\r': '\\r', - '"' : '\\"', - '\\': '\\\\' - }, - rep; - - - function quote(string) { - -// If the string contains no control characters, no quote characters, and no -// backslash characters, then we can safely slap some quotes around it. -// Otherwise we must also replace the offending characters with safe escape -// sequences. - - escapable.lastIndex = 0; - return escapable.test(string) ? - '"' + string.replace(escapable, function (a) { - var c = meta[a]; - return typeof c === 'string' ? c : - '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4); - }) + '"' : - '"' + string + '"'; - } - - - function str(key, holder) { - -// Produce a string from holder[key]. - - var i, // The loop counter. - k, // The member key. - v, // The member value. - length, - mind = gap, - partial, - value = holder[key]; - -// If the value has a toJSON method, call it to obtain a replacement value. - - if (value && typeof value === 'object' && - typeof value.toJSON === 'function') { - value = value.toJSON(key); - } - -// If we were called with a replacer function, then call the replacer to -// obtain a replacement value. - - if (typeof rep === 'function') { - value = rep.call(holder, key, value); - } - -// What happens next depends on the value's type. - - switch (typeof value) { - case 'string': - return quote(value); - - case 'number': - -// JSON numbers must be finite. Encode non-finite numbers as null. - - return isFinite(value) ? String(value) : 'null'; - - case 'boolean': - case 'null': - -// If the value is a boolean or null, convert it to a string. Note: -// typeof null does not produce 'null'. The case is included here in -// the remote chance that this gets fixed someday. - - return String(value); - -// If the type is 'object', we might be dealing with an object or an array or -// null. - - case 'object': - -// Due to a specification blunder in ECMAScript, typeof null is 'object', -// so watch out for that case. - - if (!value) { - return 'null'; - } - -// Make an array to hold the partial results of stringifying this object value. - - gap += indent; - partial = []; - -// Is the value an array? - - if (Object.prototype.toString.apply(value) === '[object Array]') { - -// The value is an array. Stringify every element. Use null as a placeholder -// for non-JSON values. - - length = value.length; - for (i = 0; i < length; i += 1) { - partial[i] = str(i, value) || 'null'; - } - -// Join all of the elements together, separated with commas, and wrap them in -// brackets. - - v = partial.length === 0 ? '[]' : - gap ? '[\n' + gap + - partial.join(',\n' + gap) + '\n' + - mind + ']' : - '[' + partial.join(',') + ']'; - gap = mind; - return v; - } - -// If the replacer is an array, use it to select the members to be stringified. - - if (rep && typeof rep === 'object') { - length = rep.length; - for (i = 0; i < length; i += 1) { - k = rep[i]; - if (typeof k === 'string') { - v = str(k, value); - if (v) { - partial.push(quote(k) + (gap ? ': ' : ':') + v); - } - } - } - } else { - -// Otherwise, iterate through all of the keys in the object. - - for (k in value) { - if (Object.hasOwnProperty.call(value, k)) { - v = str(k, value); - if (v) { - partial.push(quote(k) + (gap ? ': ' : ':') + v); - } - } - } - } - -// Join all of the member texts together, separated with commas, -// and wrap them in braces. - - v = partial.length === 0 ? '{}' : - gap ? '{\n' + gap + partial.join(',\n' + gap) + '\n' + - mind + '}' : '{' + partial.join(',') + '}'; - gap = mind; - return v; - } - } - -// If the JSON object does not yet have a stringify method, give it one. - - if (typeof JSON.stringify !== 'function') { - JSON.stringify = function (value, replacer, space) { - -// The stringify method takes a value and an optional replacer, and an optional -// space parameter, and returns a JSON text. The replacer can be a function -// that can replace values, or an array of strings that will select the keys. -// A default replacer method can be provided. Use of the space parameter can -// produce text that is more easily readable. - - var i; - gap = ''; - indent = ''; - -// If the space parameter is a number, make an indent string containing that -// many spaces. - - if (typeof space === 'number') { - for (i = 0; i < space; i += 1) { - indent += ' '; - } - -// If the space parameter is a string, it will be used as the indent string. - - } else if (typeof space === 'string') { - indent = space; - } - -// If there is a replacer, it must be a function or an array. -// Otherwise, throw an error. - - rep = replacer; - if (replacer && typeof replacer !== 'function' && - (typeof replacer !== 'object' || - typeof replacer.length !== 'number')) { - throw new Error('JSON.stringify'); - } - -// Make a fake root object containing our value under the key of ''. -// Return the result of stringifying the value. - - return str('', {'': value}); - }; - } - - -// If the JSON object does not yet have a parse method, give it one. - - if (typeof JSON.parse !== 'function') { - JSON.parse = function (text, reviver) { - -// The parse method takes a text and an optional reviver function, and returns -// a JavaScript value if the text is a valid JSON text. - - var j; - - function walk(holder, key) { - -// The walk method is used to recursively walk the resulting structure so -// that modifications can be made. - - var k, v, value = holder[key]; - if (value && typeof value === 'object') { - for (k in value) { - if (Object.hasOwnProperty.call(value, k)) { - v = walk(value, k); - if (v !== undefined) { - value[k] = v; - } else { - delete value[k]; - } - } - } - } - return reviver.call(holder, key, value); - } - - -// Parsing happens in four stages. In the first stage, we replace certain -// Unicode characters with escape sequences. JavaScript handles many characters -// incorrectly, either silently deleting them, or treating them as line endings. - - cx.lastIndex = 0; - if (cx.test(text)) { - text = text.replace(cx, function (a) { - return '\\u' + - ('0000' + a.charCodeAt(0).toString(16)).slice(-4); - }); - } - -// In the second stage, we run the text against regular expressions that look -// for non-JSON patterns. We are especially concerned with '()' and 'new' -// because they can cause invocation, and '=' because it can cause mutation. -// But just to be safe, we want to reject all unexpected forms. - -// We split the second stage into 4 regexp operations in order to work around -// crippling inefficiencies in IE's and Safari's regexp engines. First we -// replace the JSON backslash pairs with '@' (a non-JSON character). Second, we -// replace all simple value tokens with ']' characters. Third, we delete all -// open brackets that follow a colon or comma or that begin the text. Finally, -// we look to see that the remaining characters are only whitespace or ']' or -// ',' or ':' or '{' or '}'. If that is so, then the text is safe for eval. - - if (/^[\],:{}\s]*$/. -test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@'). -replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']'). -replace(/(?:^|:|,)(?:\s*\[)+/g, ''))) { - -// In the third stage we use the eval function to compile the text into a -// JavaScript structure. The '{' operator is subject to a syntactic ambiguity -// in JavaScript: it can begin a block or an object literal. We wrap the text -// in parens to eliminate the ambiguity. - - j = eval('(' + text + ')'); - -// In the optional fourth stage, we recursively walk the new structure, passing -// each name/value pair to a reviver function for possible transformation. - - return typeof reviver === 'function' ? - walk({'': j}, '') : j; - } - -// If the text is not JSON parseable, then a SyntaxError is thrown. - - throw new SyntaxError('JSON.parse'); - }; - } -}()); DELETED ajax/js/whajaj.js Index: ajax/js/whajaj.js ================================================================== --- ajax/js/whajaj.js +++ /dev/null @@ -1,1221 +0,0 @@ -/** - This file provides a JS interface into the core functionality of - JSON-centric back-ends. It sends GET or JSON POST requests to - a back-end and expects JSON responses. The exact semantics of - the underlying back-end and overlying front-end are not its concern, - and it leaves the interpretation of the data up to the client/server - insofar as possible. - - All functionality is part of a class named WhAjaj, and that class - acts as namespace for this framework. - - Author: Stephan Beal (http://wanderinghorse.net/home/stephan/) - - License: Public Domain - - This framework is directly derived from code originally found in - http://code.google.com/p/jsonmessage, and later in - http://whiki.wanderinghorse.net, where it contained quite a bit - of application-specific logic. It was eventually (the 3rd time i - needed it) split off into its own library to simplify inclusion - into my many mini-projects. -*/ - - -/** - The WhAjaj function is primarily a namespace, and not intended - to called or instantiated via the 'new' operator. -*/ -function WhAjaj() -{ -} - -/** Returns a millisecond Unix Epoch timestamp. */ -WhAjaj.msTimestamp = function() -{ - return (new Date()).getTime(); -}; - -/** Returns a Unix Epoch timestamp (in seconds) in integer format. - - Reminder to self: (1.1 %1.2) evaluates to a floating-point value - in JS, and thus this implementation is less than optimal. -*/ -WhAjaj.unixTimestamp = function() -{ - var ts = (new Date()).getTime(); - return parseInt( ""+((ts / 1000) % ts) ); -}; - -/** - Returns true if v is-a Array instance. -*/ -WhAjaj.isArray = function( v ) -{ - return (v && - (v instanceof Array) || - (Object.prototype.toString.call(v) === "[object Array]") - ); - /* Reminders to self: - typeof [] == "object" - toString.call([]) == "[object Array]" - ([]).toString() == empty - */ -}; - -/** - Returns true if v is-a Object instance. -*/ -WhAjaj.isObject = function( v ) -{ - return v && - (v instanceof Object) && - ('[object Object]' === Object.prototype.toString.apply(v) ); -}; - -/** - Returns true if v is-a Function instance. -*/ -WhAjaj.isFunction = function(obj) -{ - return obj - && ( - (obj instanceof Function) - || ('function' === typeof obj) - || ("[object Function]" === Object.prototype.toString.call(obj)) - ) - ; -}; - -/** - Parses window.location.search-style string into an object - containing key/value pairs of URL arguments (already urldecoded). - - If the str argument is not passed (arguments.length==0) then - window.location.search.substring(1) is used by default. If - neither str is passed in nor window exists then false is returned. - - On success it returns an Object containing the key/value pairs - parsed from the string. Keys which have no value are treated - has having the boolean true value. - - FIXME: for keys in the form "name[]", build an array of results, - like PHP does. - -*/ -WhAjaj.processUrlArgs = function(str) { - if( 0 === arguments.length ) { - if( ('undefined' === typeof window) || - !window.location || - !window.location.search ) return false; - else str = (''+window.location.search).substring(1); - } - if( ! str ) return false; - str = (''+str).split(/#/,2)[0]; // remove #... to avoid it being added as part of the last value. - var args = {}; - var sp = str.split(/&+/); - var rx = /^([^=]+)(=(.+))?/; - var i, m; - for( i in sp ) { - m = rx.exec( sp[i] ); - if( ! m ) continue; - args[decodeURIComponent(m[1])] = (m[3] ? decodeURIComponent(m[3]) : true); - } - return args; -}; - -/** - A simple wrapper around JSON.stringify(), using my own personal - preferred values for the 2nd and 3rd parameters. To globally - set its indentation level, assign WhAjaj.stringify.indent to - an integer value (0 for no intendation). - - This function is intended only for human-readable output, not - generic over-the-wire JSON output (where JSON.stringify(val) will - produce smaller results). -*/ -WhAjaj.stringify = function(val) { - if( ! arguments.callee.indent ) arguments.callee.indent = 4; - return JSON.stringify(val,0,arguments.callee.indent); -}; - -/** - Each instance of this class holds state information for making - AJAJ requests to a back-end system. While clients may use one - "requester" object per connection attempt, for connections to the - same back-end, using an instance configured for that back-end - can simplify usage. This class is designed so that the actual - connection-related details (i.e. _how_ it connects to the - back-end) may be re-implemented to use a client's preferred - connection mechanism (e.g. jQuery). - - The optional opt parameter may be an object with any (or all) of - the properties documented for WhAjaj.Connector.options.ajax. - Properties set here (or later via modification of the "options" - property of this object) will be used in calls to - WhAjaj.Connector.sendRequest(), and these override (normally) any - options set in WhAjaj.Connector.options.ajax. Note that - WhAjaj.Connector.sendRequest() _also_ takes an options object, - and ones passed there will override, for purposes of that one - request, any options passed in here or defined in - WhAjaj.Connector.options.ajax. See WhAjaj.Connector.options.ajax - and WhAjaj.Connector.prototype.sendRequest() for more details - about the precedence of options. - - Sample usage: - - @code - // Set up common connection-level options: - var cgi = new WhAjaj.Connector({ - url: '/cgi-bin/my.cgi', - timeout:10000, - onResponse(resp,req) { alert(JSON.stringify(resp,0.4)); }, - onError(req,opt) { - alert(opt.errorMessage); - } - }); - // Any of those options may optionally be set globally in - // WhAjaj.Connector.options.ajax (onError(), beforeSend(), and afterSend() - // are often easiest/most useful to set globally). - - // Get list of pages... - cgi.sendRequest( null, { - onResponse(resp,req){ alert(WhAjaj.stringify(resp)); } - }); - @endcode - - For common request types, clients can add functions to this - object which act as wrappers for backend-specific functionality. As - a simple example: - - @code - cgi.login = function(name,pw,ajajOpt) { - this.sendRequest( - {command:"json/login", - name:name, - password:pw - }, ajajOpt ); - }; - @endcode - - TODOs: - - - Caching of page-load requests, with a configurable lifetime. - - - Use-cases like the above login() function are a tiny bit - problematic to implement when each request has a different URL - path (i know this from the whiki and fossil implementations). - This is partly a side-effect of design descisions made back in - the very first days of this code's life. i need to go through - and see where i can bend those conventions a bit (where it won't - break my other apps unduly). -*/ -WhAjaj.Connector = function(opt) -{ - if(WhAjaj.isObject(opt)) this.options = opt; - //TODO?: this.$cache = {}; -}; - -/** - The core options used by WhAjaj.Connector instances for performing - network operations. These options can (and some _should_) - be changed by a client application. They can also be changed - on specific instances of WhAjaj.Connector, but for most applications - it is simpler to set them here and not have to bother with configuring - each WhAjaj.Connector instance. Apps which use multiple back-ends at one time, - however, will need to customize each instance for a given back-end. -*/ -WhAjaj.Connector.options = { - /** - A (meaningless) prefix to apply to WhAjaj.Connector-generated - request IDs. - */ - requestIdPrefix:'WhAjaj.Connector-', - /** - Default options for WhAjaj.Connector.sendRequest() connection - parameters. This object holds only connection-related - options and callbacks (all optional), and not options - related to the required JSON structure of any given request. - i.e. the page name used in a get-page request are not set - here but are specified as part of the request object. - - These connection options are a "normalized form" of options - often found in various AJAX libraries like jQuery, - Prototype, dojo, etc. This approach allows us to swap out - the real connection-related parts by writing a simple proxy - which transforms our "normalized" form to the - backend-specific form. For examples, see the various - implementations stored in WhAjaj.Connector.sendImpls. - - The following callback options are, in practice, almost - always set globally to some app-wide defaults: - - - onError() to report errors using a common mechanism. - - beforeSend() to start a visual activity notification - - afterSend() to disable the visual activity notification - - However, be aware that if any given WhAjaj.Connector instance is - given its own before/afterSend callback then those will - override these. Mixing shared/global and per-instance - callbacks can potentially lead to confusing results if, e.g., - the beforeSend() and afterSend() functions have side-effects - but are not used with their proper before/after partner. - - TODO: rename this to 'ajaj' (the name is historical). The - problem with renaming it is is that the word 'ajax' is - pretty prevelant in the source tree, so i can't globally - swap it out. - */ - ajax: { - /** - URL of the back-end server/CGI. - */ - url: '/some/path', - - /** - Connection method. Some connection-related functions might - override any client-defined setting. - - Must be one of 'GET' or 'POST'. For custom connection - implementation, it may optionally be some - implementation-specified value. - - Normally the API can derive this value automatically - if the - request uses JSON data it is POSTed, else it is GETted. - */ - method:'GET', - - /** - A hint whether to run the operation asynchronously or - not. Not all concrete WhAjaj.Connector.sendImpl() - implementations can support this. Interestingly, at - least one popular AJAX toolkit does not document - supporting _synchronous_ AJAX operations. All common - browser-side implementations support async operation, but - non-browser implementations might not. - */ - asynchronous:true, - - /** - A HTTP authentication login name for the AJAX - connection. Not all concrete WhAjaj.Connector.sendImpl() - implementations can support this. - */ - loginName:undefined, - - /** - An HTTP authentication login password for the AJAJ - connection. Not all concrete WhAjaj.Connector.sendImpl() - implementations can support this. - */ - loginPassword:undefined, - - /** - A connection timeout, in milliseconds, for establishing - an AJAJ connection. Not all concrete - WhAjaj.Connector.sendImpl() implementations can support this. - */ - timeout:10000, - - /** - If an AJAJ request receives JSON data from the back-end, - that data is passed as a plain Object as the response - parameter (exception: in jsonp mode it is passed a - string (why???)). The initiating request object is - passed as the second parameter, but clients can normally - ignore it (only those which need a way to map specific - requests to responses will need it). The 3rd parameter - is the same as the 'this' object for the context of the - callback, but is provided because the instance-level - callbacks (set in (WhAjaj.Connector instance).callbacks, - require it in some cases (because their 'this' is - different!). - - Note that the response might contain error information - which comes from the back-end. The difference between - this error info and the info passed to the onError() - callback is that this data indicates an - application-level error, whereas onError() is used to - report connection-level problems or when the backend - produces non-JSON data (which, when not in jsonp mode, - is unexpected and is as fatal to the request as a - connection error). - */ - onResponse: function(response, request, opt){}, - - /** - If an AJAX request fails to establish a connection or it - receives non-JSON data from the back-end, this function - is called (e.g. timeout error or host name not - resolvable). It is passed the originating request and the - "normalized" connection parameters used for that - request. The connectOpt object "should" (or "might") - have an "errorMessage" property which describes the - nature of the problem. - - Clients will almost always want to replace the default - implementation with something which integrates into - their application. - */ - onError: function(request, connectOpt) - { - alert('AJAJ request failed:\n' - +'Connection information:\n' - +JSON.stringify(connectOpt,0,4) - ); - }, - - /** - Called before each connection attempt is made. Clients - can use this to, e.g., enable a visual "network activity - notification" for the user. It is passed the original - request object and the normalized connection parameters - for the request. If this function changes opt, those - changes _are_ applied to the subsequent request. If this - function throws, neither the onError() nor afterSend() - callbacks are triggered and WhAjaj.Connector.sendImpl() - propagates the exception back to the caller. - */ - beforeSend: function(request,opt){}, - - /** - Called after an AJAJ connection attempt completes, - regardless of success or failure. Passed the same - parameters as beforeSend() (see that function for - details). - - Here's an example of setting up a visual notification on - ajax operations using jQuery (but it's also easy to do - without jQuery as well): - - @code - function startAjaxNotif(req,opt) { - var me = arguments.callee; - var c = ++me.ajaxCount; - me.element.text( c + " pending AJAX operation(s)..." ); - if( 1 == c ) me.element.stop().fadeIn(); - } - startAjaxNotif.ajaxCount = 0. - startAjaxNotif.element = jQuery('#whikiAjaxNotification'); - - function endAjaxNotif() { - var c = --startAjaxNotif.ajaxCount; - startAjaxNotif.element.text( c+" pending AJAX operation(s)..." ); - if( 0 == c ) startAjaxNotif.element.stop().fadeOut(); - } - @endcode - - Set the beforeSend/afterSend properties to those - functions to enable the notifications by default. - */ - afterSend: function(request,opt){}, - - /** - If jsonp is a string then the WhAjaj-internal response - handling code ASSUMES that the response contains a JSONP-style - construct and eval()s it after afterSend() but before onResponse(). - In this case, onResponse() will get a string value for the response - instead of a response object parsed from JSON. - */ - jsonp:undefined, - /** - Don't use yet. Planned future option. - */ - propagateExceptions:false - } -}; - - -/** - WhAjaj.Connector.prototype.callbacks defines callbacks analog - to the onXXX callbacks defined in WhAjaj.Connector.options.ajax, - with two notable differences: - - 1) these callbacks, if set, are called in addition to any - request-specific callback. The intention is to allow a framework to set - "framework-level" callbacks which should be called independently of the - request-specific callbacks (without interfering with them, e.g. - requiring special re-forwarding features). - - 2) The 'this' object in these callbacks is the Connector instance - associated with the callback, whereas the "other" onXXX form has its - "ajax options" object as its this. - - When this API says that an onXXX callback will be called for a request, - both the request's onXXX (if set) and this one (if set) will be called. -*/ -WhAjaj.Connector.prototype.callbacks = {}; -/** - Instance-specific values for AJAJ-level properties (as opposed to - application-level request properties). Options set here "override" those - specified in WhAjaj.Connector.options.ajax and are "overridden" by - options passed to sendRequest(). -*/ -WhAjaj.Connector.prototype.options = {}; - - -/** - Tries to find the given key in any of the following, returning - the first match found: opt, this.options, WhAjaj.Connector.options.ajax. - - Returns undefined if key is not found. -*/ -WhAjaj.Connector.prototype.derivedOption = function(key,opt) { - var v = opt ? opt[key] : undefined; - if( undefined !== v ) return v; - else v = this.options[key]; - if( undefined !== v ) return v; - else v = WhAjaj.Connector.options.ajax[key]; - return v; -}; - -/** - Returns a unique string on each call containing a generic - reandom request identifier string. This is not used by the core - API but can be used by client code to generate unique IDs for - each request (if needed). - - The exact format is unspecified and may change in the future. - - Request IDs can be used by clients to "match up" responses to - specific requests if needed. In practice, however, they are - seldom, if ever, needed. When passing several concurrent - requests through the same response callback, it might be useful - for some clients to be able to distinguish, possibly re-routing - them through other handlers based on the originating request type. - - If this.options.requestIdPrefix or - WhAjaj.Connector.options.requestIdPrefix is set then that text - is prefixed to the returned string. -*/ -WhAjaj.Connector.prototype.generateRequestId = function() -{ - if( undefined === arguments.callee.sequence ) - { - arguments.callee.sequence = 0; - } - var pref = this.options.requestIdPrefix || WhAjaj.Connector.options.requestIdPrefix || ''; - return pref + - WhAjaj.msTimestamp() + - '/'+(Math.round( Math.random() * 100000000) )+ - ':'+(++arguments.callee.sequence); -}; - -/** - Copies (SHALLOWLY) all properties in opt to this.options. -*/ -WhAjaj.Connector.prototype.addOptions = function(opt) { - var k, v; - for( k in opt ) { - if( ! opt.hasOwnProperty(k) ) continue /* proactive Prototype kludge! */; - this.options[k] = opt[k]; - } - return this.options; -}; - -/** - An internal helper object which holds several functions intended - to simplify the creation of concrete communication channel - implementations for WhAjaj.Connector.sendImpl(). These operations - take care of some of the more error-prone parts of ensuring that - onResponse(), onError(), etc. callbacks are called consistently - using the same rules. -*/ -WhAjaj.Connector.sendHelper = { - /** - opt is assumed to be a normalized set of - WhAjaj.Connector.sendRequest() options. This function - creates a url by concatenating opt.url and some form of - opt.urlParam. - - If opt.urlParam is an object or string then it is appended - to the url. An object is assumed to be a one-dimensional set - of simple (urlencodable) key/value pairs, and not larger - data structures. A string value is assumed to be a - well-formed, urlencoded set of key/value pairs separated by - '&' characters. - - The new/normalized URL is returned (opt is not modified). If - opt.urlParam is not set then opt.url is returned (or an - empty string if opt.url is itself a false value). - - TODO: if opt is-a Object and any key points to an array, - build up a list of keys in the form "keyname[]". We could - arguably encode sub-objects like "keyname[subkey]=...", but - i don't know if that's conventions-compatible with other - frameworks. - */ - normalizeURL: function(opt) { - var u = opt.url || ''; - if( opt.urlParam ) { - var addQ = (u.indexOf('?') >= 0) ? false : true; - var addA = addQ ? false : ((u.indexOf('&')>=0) ? true : false); - var tail = ''; - if( WhAjaj.isObject(opt.urlParam) ) { - var li = [], k; - for( k in opt.urlParam) { - li.push( k+'='+encodeURIComponent( opt.urlParam[k] ) ); - } - tail = li.join('&'); - } - else if( 'string' === typeof opt.urlParam ) { - tail = opt.urlParam; - } - u = u + (addQ ? '?' : '') + (addA ? '&' : '') + tail; - } - return u; - }, - /** - Should be called by WhAjaj.Connector.sendImpl() - implementations after a response has come back. This - function takes care of most of ensuring that framework-level - conventions involving WhAjaj.Connector.options.ajax - properties are followed. - - The request argument must be the original request passed to - the sendImpl() function. It may legally be null for GET requests. - - The opt object should be the normalized AJAX options used - for the connection. - - The resp argument may be either a plain Object or a string - (in which case it is assumed to be JSON). - - The 'this' object for this call MUST be a WhAjaj.Connector - instance in order for callback processing to work properly. - - This function takes care of the following: - - - Calling opt.afterSend() - - - If resp is a string, de-JSON-izing it to an object. - - - Calling opt.onResponse() - - - Calling opt.onError() in several common (potential) error - cases. - - - If resp is-a String and opt.jsonp then resp is assumed to be - a JSONP-form construct and is eval()d BEFORE opt.onResponse() - is called. It is arguable to eval() it first, but the logic - integrates better with the non-jsonp handler. - - The sendImpl() should return immediately after calling this. - - The sendImpl() must call only one of onSendSuccess() or - onSendError(). It must call one of them or it must implement - its own response/error handling, which is not recommended - because getting the documented semantics of the - onError/onResponse/afterSend handling correct can be tedious. - */ - onSendSuccess:function(request,resp,opt) { - var cb = this.callbacks || {}; - if( WhAjaj.isFunction(cb.afterSend) ) { - try {cb.afterSend( request, opt );} - catch(e){} - } - if( WhAjaj.isFunction(opt.afterSend) ) { - try {opt.afterSend( request, opt );} - catch(e){} - } - function doErr(){ - if( WhAjaj.isFunction(cb.onError) ) { - try {cb.onError( request, opt );} - catch(e){} - } - if( WhAjaj.isFunction(opt.onError) ) { - try {opt.onError( request, opt );} - catch(e){} - } - } - if( ! resp ) { - opt.errorMessage = "Sending of request succeeded but returned no data!"; - doErr(); - return false; - } - - if( 'string' === typeof resp ) { - try { - resp = opt.jsonp ? eval(resp) : JSON.parse(resp); - } catch(e) { - opt.errorMessage = e.toString(); - doErr(); - return; - } - } - try { - if( WhAjaj.isFunction( cb.onResponse ) ) { - cb.onResponse( resp, request, opt ); - } - if( WhAjaj.isFunction( opt.onResponse ) ) { - opt.onResponse( resp, request, opt ); - } - return true; - } - catch(e) { - opt.errorMessage = "Exception while handling inbound JSON response:\n" - + e - +"\nOriginal response data:\n"+JSON.stringify(resp,0,2) - ; - ; - doErr(); - return false; - } - }, - /** - Should be called by sendImpl() implementations after a response - has failed to connect (e.g. could not resolve host or timeout - reached). This function takes care of most of ensuring that - framework-level conventions involving WhAjaj.Connector.options.ajax - properties are followed. - - The request argument must be the original request passed to - the sendImpl() function. It may legally be null for GET - requests. - - The 'this' object for this call MUST be a WhAjaj.Connector - instance in order for callback processing to work properly. - - The opt object should be the normalized AJAX options used - for the connection. By convention, the caller of this - function "should" set opt.errorMessage to contain a - human-readable description of the error. - - The sendImpl() should return immediately after calling this. The - return value from this function is unspecified. - */ - onSendError: function(request,opt) { - var cb = this.callbacks || {}; - if( WhAjaj.isFunction(cb.afterSend) ) { - try {cb.afterSend( request, opt );} - catch(e){} - } - if( WhAjaj.isFunction(opt.afterSend) ) { - try {opt.afterSend( request, opt );} - catch(e){} - } - if( WhAjaj.isFunction( cb.onError ) ) { - try {cb.onError( request, opt );} - catch(e) {/*ignore*/} - } - if( WhAjaj.isFunction( opt.onError ) ) { - try {opt.onError( request, opt );} - catch(e) {/*ignore*/} - } - } -}; - -/** - WhAjaj.Connector.sendImpls holds several concrete - implementations of WhAjaj.Connector.prototype.sendImpl(). To use - a specific implementation by default assign - WhAjaj.Connector.prototype.sendImpl to one of these functions. - - The functions defined here require that the 'this' object be-a - WhAjaj.Connector instance. - - Historical notes: - - a) We once had an implementation based on Prototype, but that - library just pisses me off (they change base-most types' - prototypes, introducing side-effects in client code which - doesn't even use Prototype). The Prototype version at the time - had a serious toJSON() bug which caused empty arrays to - serialize as the string "[]", which broke a bunch of my code. - (That has been fixed in the mean time, but i don't use - Prototype.) - - b) We once had an implementation for the dojo library, - - If/when the time comes to add Prototype/dojo support, we simply - need to port: - - http://code.google.com/p/jsonmessage/source/browse/trunk/lib/JSONMessage/JSONMessage.inc.js - - (search that file for "dojo" and "Prototype") to this tree. That - code is this code's generic grandfather and they are still very - similar, so a port is trivial. - -*/ -WhAjaj.Connector.sendImpls = { - /** - This is a concrete implementation of - WhAjaj.Connector.prototype.sendImpl() which uses the - environment's native XMLHttpRequest class to send whiki - requests and fetch the responses. - - The only argument must be a connection properties object, as - constructed by WhAjaj.Connector.normalizeAjaxParameters(). - - If window.firebug is set then window.firebug.watchXHR() is - called to enable monitoring of the XMLHttpRequest object. - - This implementation honors the loginName and loginPassword - connection parameters. - - Returns the XMLHttpRequest object. - - This implementation requires that the 'this' object be-a - WhAjaj.Connector. - - This implementation uses setTimeout() to implement the - timeout support, and thus the JS engine must provide that - functionality. - */ - XMLHttpRequest: function(request, args) - { - var json = WhAjaj.isObject(request) ? JSON.stringify(request) : request; - var xhr = new XMLHttpRequest(); - var startTime = (new Date()).getTime(); - var timeout = args.timeout || 10000/*arbitrary!*/; - var hitTimeout = false; - var done = false; - var tmid /* setTimeout() ID */; - var whself = this; - function handleTimeout() - { - hitTimeout = true; - if( ! done ) - { - var now = (new Date()).getTime(); - try { xhr.abort(); } catch(e) {/*ignore*/} - // see: http://www.w3.org/TR/XMLHttpRequest/#the-abort-method - args.errorMessage = "Timeout of "+timeout+"ms reached after "+(now-startTime)+"ms during AJAX request."; - WhAjaj.Connector.sendHelper.onSendError.apply( whself, [request, args] ); - } - return; - } - function onStateChange() - { // reminder to self: apparently 'this' is-not-a XHR :/ - if( hitTimeout ) - { /* we're too late - the error was already triggered. */ - return; - } - - if( 4 == xhr.readyState ) - { - done = true; - if( tmid ) - { - clearTimeout( tmid ); - tmid = null; - } - if( (xhr.status >= 200) && (xhr.status < 300) ) - { - WhAjaj.Connector.sendHelper.onSendSuccess.apply( whself, [request, xhr.responseText, args] ); - return; - } - else - { - if( undefined === args.errorMessage ) - { - args.errorMessage = "Error sending a '"+args.method+"' AJAX request to " - +"["+args.url+"]: " - +"Status text=["+xhr.statusText+"]" - ; - WhAjaj.Connector.sendHelper.onSendError.apply( whself, [request, args] ); - } - else { /*maybe it was was set by the timeout handler. */ } - return; - } - } - }; - - xhr.onreadystatechange = onStateChange; - if( ('undefined'!==(typeof window)) && ('firebug' in window) && ('watchXHR' in window.firebug) ) - { /* plug in to firebug lite's XHR monitor... */ - window.firebug.watchXHR( xhr ); - } - try - { - //alert( JSON.stringify( args )); - function xhrOpen() - { - if( ('loginName' in args) && args.loginName ) - { - xhr.open( args.method, args.url, args.asynchronous, args.loginName, args.loginPassword ); - } - else - { - xhr.open( args.method, args.url, args.asynchronous ); - } - } - if( json && ('POST' === args.method.toUpperCase()) ) - { - xhrOpen(); - xhr.setRequestHeader("Content-Type", "application/json; charset=utf-8"); - // Google Chrome warns that it refuses to set these - // "unsafe" headers (his words, not mine): - // xhr.setRequestHeader("Content-length", json.length); - // xhr.setRequestHeader("Connection", "close"); - xhr.send( json ); - } - else /* assume GET */ - { - xhrOpen(); - xhr.send(null); - } - tmid = setTimeout( handleTimeout, timeout ); - return xhr; - } - catch(e) - { - args.errorMessage = e.toString(); - WhAjaj.Connector.sendHelper.onSendError.apply( whself, [request, args] ); - return undefined; - } - }/*XMLHttpRequest()*/, - /** - This is a concrete implementation of - WhAjaj.Connector.prototype.sendImpl() which uses the jQuery - AJAX API to send requests and fetch the responses. - - The first argument may be either null/false, an Object - containing toJSON-able data to post to the back-end, or such an - object in JSON string form. - - The second argument must be a connection properties object, as - constructed by WhAjaj.Connector.normalizeAjaxParameters(). - - If window.firebug is set then window.firebug.watchXHR() is - called to enable monitoring of the XMLHttpRequest object. - - This implementation honors the loginName and loginPassword - connection parameters. - - Returns the XMLHttpRequest object. - - This implementation requires that the 'this' object be-a - WhAjaj.Connector. - */ - jQuery:function(request,args) - { - var data = request || undefined; - var whself = this; - if( data ) { - if('string'!==typeof data) { - try { - data = JSON.stringify(data); - } - catch(e) { - WhAjaj.Connector.sendHelper.onSendError.apply( whself, [request, args] ); - return; - } - } - } - var ajopt = { - url: args.url, - data: data, - type: args.method, - async: args.asynchronous, - password: (undefined !== args.loginPassword) ? args.loginPassword : undefined, - username: (undefined !== args.loginName) ? args.loginName : undefined, - contentType: 'application/json; charset=utf-8', - error: function(xhr, textStatus, errorThrown) - { - //this === the options for this ajax request - args.errorMessage = "Error sending a '"+ajopt.type+"' request to ["+ajopt.url+"]: " - +"Status text=["+textStatus+"]" - +(errorThrown ? ("Error=["+errorThrown+"]") : "") - ; - WhAjaj.Connector.sendHelper.onSendError.apply( whself, [request, args] ); - }, - success: function(data) - { - WhAjaj.Connector.sendHelper.onSendSuccess.apply( whself, [request, data, args] ); - }, - /* Set dataType=text instead of json to keep jQuery from doing our carefully - written response handling for us. - */ - dataType: 'text' - }; - if( undefined !== args.timeout ) - { - ajopt.timeout = args.timeout; - } - try - { - return jQuery.ajax(ajopt); - } - catch(e) - { - args.errorMessage = e.toString(); - WhAjaj.Connector.sendHelper.onSendError.apply( whself, [request, args] ); - return undefined; - } - }/*jQuery()*/, - /** - This is a concrete implementation of - WhAjaj.Connector.prototype.sendImpl() which uses the rhino - Java API to send requests and fetch the responses. - - Limitations vis-a-vis the interface: - - - timeouts are not supported. - - - asynchronous mode is not supported because implementing it - requires the ability to kill a running thread (which is deprecated - in the Java API). - - TODOs: - - - add socket timeouts. - - - support HTTP proxy. - - The Java APIs support this, it just hasn't been added here yet. - */ - rhino:function(request,args) - { - var self = this; - var data = request || undefined; - if( data ) { - if('string'!==typeof data) { - try { - data = JSON.stringify(data); - } - catch(e) { - WhAjaj.Connector.sendHelper.onSendError.apply( self, [request, args] ); - return; - } - } - } - var url; - var con; - var IO = new JavaImporter(java.io); - var wr; - var rd, ln, json = []; - function setIncomingCookies(list){ - if(!list || !list.length) return; - if( !self.cookies ) self.cookies = {}; - var k, v, i; - for( i = 0; i < list.length; ++i ){ - v = list[i].split('=',2); - k = decodeURIComponent(v[0]) - v = v[0] ? decodeURIComponent(v[0].split(';',2)[0]) : null; - //print("RECEIVED COOKIE: "+k+"="+v); - if(!v) { - delete self.cookies[k]; - continue; - }else{ - self.cookies[k] = v; - } - } - }; - function setOutboundCookies(conn){ - if(!self.cookies) return; - var k, v; - for( k in self.cookies ){ - if(!self.cookies.hasOwnProperty(k)) continue /*kludge for broken JS libs*/; - v = self.cookies[k]; - conn.addRequestProperty("Cookie", encodeURIComponent(k)+'='+encodeURIComponent(v)); - //print("SENDING COOKIE: "+k+"="+v); - } - }; - try{ - url = new java.net.URL( args.url ) - con = url.openConnection(/*FIXME: add proxy support!*/); - con.setRequestProperty("Accept-Charset","utf-8"); - setOutboundCookies(con); - if(data){ - con.setRequestProperty("Content-Type","application/json; charset=utf-8"); - con.setDoOutput( true ); - wr = new IO.OutputStreamWriter(con.getOutputStream()) - wr.write(data); - wr.flush(); - wr.close(); - wr = null; - //print("POSTED: "+data); - } - rd = new IO.BufferedReader(new IO.InputStreamReader(con.getInputStream())); - //var skippedHeaders = false; - while ((line = rd.readLine()) !== null) { - //print("LINE: "+line); - //if(!line.length && !skippedHeaders){ - // skippedHeaders = true; - // json = []; - // continue; - //} - json.push(line); - } - setIncomingCookies(con.getHeaderFields().get("Set-Cookie")); - }catch(e){ - args.errorMessage = e.toString(); - WhAjaj.Connector.sendHelper.onSendError.apply( self, [request, args] ); - return undefined; - } - try { if(wr) wr.close(); } catch(e) { /*ignore*/} - try { if(rd) rd.close(); } catch(e) { /*ignore*/} - json = json.join(''); - //print("READ IN JSON: "+json); - WhAjaj.Connector.sendHelper.onSendSuccess.apply( self, [request, json, args] ); - }/*rhino*/ -}; - -/** - An internal function which takes an object containing properties - for a WhAjaj.Connector network request. This function creates a new - object containing a superset of the properties from: - - a) opt - b) this.options - c) WhAjaj.Connector.options.ajax - - in that order, using the first one it finds. - - All non-function properties are _deeply_ copied via JSON cloning - in order to prevent accidental "cross-request pollenation" (been - there, done that). Functions cannot be cloned and are simply - copied by reference. - - This function throws if JSON-copying one of the options fails - (e.g. due to cyclic data structures). - - Reminder to self: this function does not "normalize" opt.urlParam - by encoding it into opt.url, mainly for historical reasons, but - also because that behaviour was specifically undesirable in this - code's genetic father. -*/ -WhAjaj.Connector.prototype.normalizeAjaxParameters = function (opt) -{ - var rc = {}; - function merge(k,v) - { - if( rc.hasOwnProperty(k) ) return; - else if( WhAjaj.isFunction(v) ) {} - else if( WhAjaj.isObject(v) ) v = JSON.parse( JSON.stringify(v) ); - rc[k]=v; - } - function cp(obj) { - if( ! WhAjaj.isObject(obj) ) return; - var k; - for( k in obj ) { - if( ! obj.hasOwnProperty(k) ) continue /* i will always hate the Prototype designers for this. */; - merge(k, obj[k]); - } - } - cp( opt ); - cp( this.options ); - cp( WhAjaj.Connector.options.ajax ); - // no, not here: rc.url = WhAjaj.Connector.sendHelper.normalizeURL(rc); - return rc; -}; - -/** - This is the generic interface for making calls to a back-end - JSON-producing request handler. It is a simple wrapper around - WhAjaj.Connector.prototype.sendImpl(), which just normalizes the - connection options for sendImpl() and makes sure that - opt.beforeSend() is (possibly) called. - - The request parameter must either be false/null/empty or a - fully-populated JSON-able request object (which will be sent as - unencoded application/json text), depending on the type of - request being made. It is never semantically legal (in this API) - for request to be a string/number/true/array value. As a rule, - only POST requests use the request data. GET requests should - encode their data in opt.url or opt.urlParam (see below). - - opt must contain the network-related parameters for the request. - Paramters _not_ set in opt are pulled from this.options or - WhAjaj.Connector.options.ajax (in that order, using the first - value it finds). Thus the set of connection-level options used - for the request are a superset of those various sources. - - The "normalized" (or "superimposed") opt object's URL may be - modified before the request is sent, as follows: - - if opt.urlParam is a string then it is assumed to be properly - URL-encoded parameters and is appended to the opt.url. If it is - an Object then it is assumed to be a one-dimensional set of - key/value pairs with simple values (numbers, strings, booleans, - null, and NOT objects/arrays). The keys/values are URL-encoded - and appended to the URL. - - The beforeSend() callback (see below) can modify the options - object before the request attempt is made. - - The callbacks in the normalized opt object will be triggered as - follows (if they are set to Function values): - - - beforeSend(request,opt) will be called before any network - processing starts. If beforeSend() throws then no other - callbacks are triggered and this function propagates the - exception. This function is passed normalized connection options - as its second parameter, and changes this function makes to that - object _will_ be used for the pending connection attempt. - - - onError(request,opt) will be called if a connection to the - back-end cannot be established. It will be passed the original - request object (which might be null, depending on the request - type) and the normalized options object. In the error case, the - opt object passed to onError() "should" have a property called - "errorMessage" which contains a description of the problem. - - - onError(request,opt) will also be called if connection - succeeds but the response is not JSON data. - - - onResponse(response,request) will be called if the response - returns JSON data. That data might hold an error response code - - clients need to check for that. It is passed the response object - (a plain object) and the original request object. - - - afterSend(request,opt) will be called directly after the - AJAX request is finished, before onError() or onResonse() are - called. Possible TODO: we explicitly do NOT pass the response to - this function in order to keep the line between the responsibilities - of the various callback clear (otherwise this could be used the same - as onResponse()). In practice it would sometimes be useful have the - response passed to this function, mainly for logging/debugging - purposes. - - The return value from this function is meaningless because - AJAX operations tend to take place asynchronously. - -*/ -WhAjaj.Connector.prototype.sendRequest = function(request,opt) -{ - if( !WhAjaj.isFunction(this.sendImpl) ) - { - throw new Error("This object has no sendImpl() member function! I don't know how to send the request!"); - } - var ex = false; - var av = Array.prototype.slice.apply( arguments, [0] ); - - /** - FIXME: how to handle the error, vis-a-vis- the callbacks, if - normalizeAjaxParameters() throws? It can throw if - (de)JSON-izing fails. - */ - var norm = this.normalizeAjaxParameters( WhAjaj.isObject(opt) ? opt : {} ); - norm.url = WhAjaj.Connector.sendHelper.normalizeURL(norm); - if( ! request ) norm.method = 'GET'; - var cb = this.callbacks || {}; - if( this.callbacks && WhAjaj.isFunction(this.callbacks.beforeSend) ) { - this.callbacks.beforeSend( request, norm ); - } - if( WhAjaj.isFunction(norm.beforeSend) ){ - norm.beforeSend( request, norm ); - } - //alert( WhAjaj.stringify(request)+'\n'+WhAjaj.stringify(norm)); - try { this.sendImpl( request, norm ); } - catch(e) { ex = e; } - if(ex) throw ex; -}; - -/** - sendImpl() holds a concrete back-end connection implementation. It - can be replaced with a custom implementation if one follows the rules - described throughout this API. See WhAjaj.Connector.sendImpls for - the concrete implementations included with this API. -*/ -//WhAjaj.Connector.prototype.sendImpl = WhAjaj.Connector.sendImpls.XMLHttpRequest; -//WhAjaj.Connector.prototype.sendImpl = WhAjaj.Connector.sendImpls.rhino; -//WhAjaj.Connector.prototype.sendImpl = WhAjaj.Connector.sendImpls.jQuery; - -if( 'undefined' !== typeof jQuery ){ - WhAjaj.Connector.prototype.sendImpl = WhAjaj.Connector.sendImpls.jQuery; -} -else { - WhAjaj.Connector.prototype.sendImpl = WhAjaj.Connector.sendImpls.XMLHttpRequest; -} DELETED ajax/wiki-editor.html Index: ajax/wiki-editor.html ================================================================== --- ajax/wiki-editor.html +++ /dev/null @@ -1,381 +0,0 @@ - - - - - Fossil/JSON Wiki Editor Prototype - - - - - - - - - - - - - -

PROTOTYPE JSON-based Fossil Wiki Editor

- -See also: main test page. - -
-Login: -
- -or: -name: -pw: - - - -
- - -
-Quick-posts:
- - - - - - - -
- - - - - - - - - - - - - - - - -
Page ListContent
-
-
-
-
- -
Response
- -
-
-
-
- - DELETED art/encode1.tex Index: art/encode1.tex ================================================================== --- art/encode1.tex +++ /dev/null @@ -1,2 +0,0 @@ -\LARGE A = (\sum_{i=0}^{NHASH-1} z_i) \bmod 2^{16} - DELETED art/encode2.tex Index: art/encode2.tex ================================================================== --- art/encode2.tex +++ /dev/null @@ -1,1 +0,0 @@ -\LARGE B = (\sum_{i=0}^{NHASH-1} (NHASH-i)z_i) \bmod 2^{16} DELETED art/encode3.tex Index: art/encode3.tex ================================================================== --- art/encode3.tex +++ /dev/null @@ -1,1 +0,0 @@ -\LARGE V = 2^{16}B + A DELETED art/encode4.tex Index: art/encode4.tex ================================================================== --- art/encode4.tex +++ /dev/null @@ -1,1 +0,0 @@ -\LARGE z_0 DELETED art/encode5.tex Index: art/encode5.tex ================================================================== --- art/encode5.tex +++ /dev/null @@ -1,1 +0,0 @@ -\LARGE z_{new} DELETED art/encode6.tex Index: art/encode6.tex ================================================================== --- art/encode6.tex +++ /dev/null @@ -1,1 +0,0 @@ -\LARGE A_{new} = (A - z_0 + z_{new}) \bmod 2^{16} DELETED art/encode7.tex Index: art/encode7.tex ================================================================== --- art/encode7.tex +++ /dev/null @@ -1,1 +0,0 @@ -\LARGE B_{new} = (B - z_0 NHASH + A_{new}) \bmod 2^{16} DELETED art/encode8.tex Index: art/encode8.tex ================================================================== --- art/encode8.tex +++ /dev/null @@ -1,1 +0,0 @@ -\LARGE V_{new} = 2^{16}B_{new} + A_{new} DELETED art/encode9.tex Index: art/encode9.tex ================================================================== --- art/encode9.tex +++ /dev/null @@ -1,1 +0,0 @@ -\LARGE A_{new} Index: auto.def ================================================================== --- auto.def +++ auto.def @@ -186,11 +186,11 @@ # passes MINIMUM_SQLITE_VERSION set at the top of this file to sqlcompttest.c # set cmdline {} lappend cmdline {*}[get-define CCACHE] lappend cmdline {*}[get-define CC] {*}[get-define CFLAGS] - lappend cmdline $::autosetup(dir)/../src/sqlcompattest.c -o conftest__ + lappend cmdline $::autosetup(dir)/../tools/sqlcompattest.c -o conftest__ lappend cmdline {*}[get-define LDFLAGS] lappend cmdline {*}[get-define LIBS] set sqlite-version [string cat "-D MINIMUM_SQLITE_VERSION=" [get-define MINIMUM_SQLITE_VERSION]] lappend cmdline {*}[set sqlite-version] set ok 1 @@ -471,11 +471,11 @@ set sq3path [opt-val with-sqlite] if {$sq3path in {tree ""}} { msg-result "Using sqlite3.c from this source tree." } else { # SQLITE3_ORIGIN: - # 0 = (local source tree) + # 0 = local source tree # 1 = use external lib or sqlite3.o # 2 = use external sqlite3.c and (if found) shell.c define USE_SYSTEM_SQLITE 1 define SQLITE3_SRC.2 {} define SQLITE3_OBJ.2 {} @@ -521,11 +521,11 @@ } } elseif {![cc-check-includes sqlite3.h] || ![check-function-in-lib sqlite3_open_v2 sqlite3]} { user-error "libsqlite3 not found please install it or specify the location with --with-sqlite" } } -define-append CFLAGS_INCLUDE {-I. -I$(SRCDIR)} +define-append CFLAGS_INCLUDE {-I. -I$(SRCDIR) -I$(SRCDIR_extsrc)} set tclpath [opt-val with-tcl] if {$tclpath ne ""} { set tclprivatestubs [opt-bool with-tcl-private-stubs] # Note parse-tclconfig-sh is in autosetup/local.tcl ADDED extsrc/cson_amalgamation.c Index: extsrc/cson_amalgamation.c ================================================================== --- /dev/null +++ extsrc/cson_amalgamation.c @@ -0,0 +1,5710 @@ +#ifdef FOSSIL_ENABLE_JSON +/* auto-generated! Do not edit! */ +#include "cson_amalgamation.h" +/* begin file parser/JSON_parser.h */ +/* See JSON_parser.c for copyright information and licensing. */ + +#ifndef JSON_PARSER_H +#define JSON_PARSER_H + +/* JSON_parser.h */ + + +#include + +/* Windows DLL stuff */ +#ifdef JSON_PARSER_DLL +# ifdef _MSC_VER +# ifdef JSON_PARSER_DLL_EXPORTS +# define JSON_PARSER_DLL_API __declspec(dllexport) +# else +# define JSON_PARSER_DLL_API __declspec(dllimport) +# endif +# else +# define JSON_PARSER_DLL_API +# endif +#else +# define JSON_PARSER_DLL_API +#endif + +/* Determine the integer type use to parse non-floating point numbers */ +#ifdef _WIN32 +typedef __int64 JSON_int_t; +#define JSON_PARSER_INTEGER_SSCANF_TOKEN "%I64d" +#define JSON_PARSER_INTEGER_SPRINTF_TOKEN "%I64d" +#elif (__STDC_VERSION__ >= 199901L) || (HAVE_LONG_LONG == 1) +typedef long long JSON_int_t; +#define JSON_PARSER_INTEGER_SSCANF_TOKEN "%lld" +#define JSON_PARSER_INTEGER_SPRINTF_TOKEN "%lld" +#else +typedef long JSON_int_t; +#define JSON_PARSER_INTEGER_SSCANF_TOKEN "%ld" +#define JSON_PARSER_INTEGER_SPRINTF_TOKEN "%ld" +#endif + + +#ifdef __cplusplus +extern "C" { +#endif + +typedef enum +{ + JSON_E_NONE = 0, + JSON_E_INVALID_CHAR, + JSON_E_INVALID_KEYWORD, + JSON_E_INVALID_ESCAPE_SEQUENCE, + JSON_E_INVALID_UNICODE_SEQUENCE, + JSON_E_INVALID_NUMBER, + JSON_E_NESTING_DEPTH_REACHED, + JSON_E_UNBALANCED_COLLECTION, + JSON_E_EXPECTED_KEY, + JSON_E_EXPECTED_COLON, + JSON_E_OUT_OF_MEMORY +} JSON_error; + +typedef enum +{ + JSON_T_NONE = 0, + JSON_T_ARRAY_BEGIN, + JSON_T_ARRAY_END, + JSON_T_OBJECT_BEGIN, + JSON_T_OBJECT_END, + JSON_T_INTEGER, + JSON_T_FLOAT, + JSON_T_NULL, + JSON_T_TRUE, + JSON_T_FALSE, + JSON_T_STRING, + JSON_T_KEY, + JSON_T_MAX +} JSON_type; + +typedef struct JSON_value_struct { + union { + JSON_int_t integer_value; + + double float_value; + + struct { + const char* value; + size_t length; + } str; + } vu; +} JSON_value; + +typedef struct JSON_parser_struct* JSON_parser; + +/*! \brief JSON parser callback + + \param ctx The pointer passed to new_JSON_parser. + \param type An element of JSON_type but not JSON_T_NONE. + \param value A representation of the parsed value. This parameter is NULL for + JSON_T_ARRAY_BEGIN, JSON_T_ARRAY_END, JSON_T_OBJECT_BEGIN, JSON_T_OBJECT_END, + JSON_T_NULL, JSON_T_TRUE, and JSON_T_FALSE. String values are always returned + as zero-terminated C strings. + + \return Non-zero if parsing should continue, else zero. +*/ +typedef int (*JSON_parser_callback)(void* ctx, int type, const JSON_value* value); + + +/** + A typedef for allocator functions semantically compatible with malloc(). +*/ +typedef void* (*JSON_malloc_t)(size_t n); +/** + A typedef for deallocator functions semantically compatible with free(). +*/ +typedef void (*JSON_free_t)(void* mem); + +/*! \brief The structure used to configure a JSON parser object +*/ +typedef struct { + /** Pointer to a callback, called when the parser has something to tell + the user. This parameter may be NULL. In this case the input is + merely checked for validity. + */ + JSON_parser_callback callback; + /** + Callback context - client-specified data to pass to the + callback function. This parameter may be NULL. + */ + void* callback_ctx; + /** Specifies the levels of nested JSON to allow. Negative numbers yield unlimited nesting. + If negative, the parser can parse arbitrary levels of JSON, otherwise + the depth is the limit. + */ + int depth; + /** + To allow C style comments in JSON, set to non-zero. + */ + int allow_comments; + /** + To decode floating point numbers manually set this parameter to + non-zero. + */ + int handle_floats_manually; + /** + The memory allocation routine, which must be semantically + compatible with malloc(3). If set to NULL, malloc(3) is used. + + If this is set to a non-NULL value then the 'free' member MUST be + set to the proper deallocation counterpart for this function. + Failure to do so results in undefined behaviour at deallocation + time. + */ + JSON_malloc_t malloc; + /** + The memory deallocation routine, which must be semantically + compatible with free(3). If set to NULL, free(3) is used. + + If this is set to a non-NULL value then the 'alloc' member MUST be + set to the proper allocation counterpart for this function. + Failure to do so results in undefined behaviour at deallocation + time. + */ + JSON_free_t free; +} JSON_config; + +/*! \brief Initializes the JSON parser configuration structure to default values. + + The default configuration is + - 127 levels of nested JSON (depends on JSON_PARSER_STACK_SIZE, see json_parser.c) + - no parsing, just checking for JSON syntax + - no comments + - Uses realloc() for memory de/allocation. + + \param config. Used to configure the parser. +*/ +JSON_PARSER_DLL_API void init_JSON_config(JSON_config * config); + +/*! \brief Create a JSON parser object + + \param config. Used to configure the parser. Set to NULL to use + the default configuration. See init_JSON_config. Its contents are + copied by this function, so it need not outlive the returned + object. + + \return The parser object, which is owned by the caller and must eventually + be freed by calling delete_JSON_parser(). +*/ +JSON_PARSER_DLL_API JSON_parser new_JSON_parser(JSON_config const* config); + +/*! \brief Destroy a previously created JSON parser object. */ +JSON_PARSER_DLL_API void delete_JSON_parser(JSON_parser jc); + +/*! \brief Parse a character. + + \return Non-zero, if all characters passed to this function are part of are valid JSON. +*/ +JSON_PARSER_DLL_API int JSON_parser_char(JSON_parser jc, int next_char); + +/*! \brief Finalize parsing. + + Call this method once after all input characters have been consumed. + + \return Non-zero, if all parsed characters are valid JSON, zero otherwise. +*/ +JSON_PARSER_DLL_API int JSON_parser_done(JSON_parser jc); + +/*! \brief Determine if a given string is valid JSON white space + + \return Non-zero if the string is valid, zero otherwise. +*/ +JSON_PARSER_DLL_API int JSON_parser_is_legal_white_space_string(const char* s); + +/*! \brief Gets the last error that occurred during the use of JSON_parser. + + \return A value from the JSON_error enum. +*/ +JSON_PARSER_DLL_API int JSON_parser_get_last_error(JSON_parser jc); + +/*! \brief Re-sets the parser to prepare it for another parse run. + + \return True (non-zero) on success, 0 on error (e.g. !jc). +*/ +JSON_PARSER_DLL_API int JSON_parser_reset(JSON_parser jc); + + +#ifdef __cplusplus +} +#endif + + +#endif /* JSON_PARSER_H */ +/* end file parser/JSON_parser.h */ +/* begin file parser/JSON_parser.c */ +/* +Copyright (c) 2007-2013 Jean Gressmann (jean@0x42.de) + +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. +*/ + +/* + Changelog: + 2013-09-08 + Updated license to to be compatible with Debian license requirements. + + 2012-06-06 + Fix for invalid UTF16 characters and some comment fixex (thomas.h.moog@intel.com). + + 2010-11-25 + Support for custom memory allocation (sgbeal@googlemail.com). + + 2010-05-07 + Added error handling for memory allocation failure (sgbeal@googlemail.com). + Added diagnosis errors for invalid JSON. + + 2010-03-25 + Fixed buffer overrun in grow_parse_buffer & cleaned up code. + + 2009-10-19 + Replaced long double in JSON_value_struct with double after reports + of strtold being broken on some platforms (charles@transmissionbt.com). + + 2009-05-17 + Incorporated benrudiak@googlemail.com fix for UTF16 decoding. + + 2009-05-14 + Fixed float parsing bug related to a locale being set that didn't + use '.' as decimal point character (charles@transmissionbt.com). + + 2008-10-14 + Renamed states.IN to states.IT to avoid name clash which IN macro + defined in windef.h (alexey.pelykh@gmail.com) + + 2008-07-19 + Removed some duplicate code & debugging variable (charles@transmissionbt.com) + + 2008-05-28 + Made JSON_value structure ansi C compliant. This bug was report by + trisk@acm.jhu.edu + + 2008-05-20 + Fixed bug reported by charles@transmissionbt.com where the switching + from static to dynamic parse buffer did not copy the static parse + buffer's content. +*/ + + + +#include +#include +#include +#include +#include +#include +#include +#include + + +#ifdef _MSC_VER +# if _MSC_VER >= 1400 /* Visual Studio 2005 and up */ +# pragma warning(disable:4996) /* unsecure sscanf */ +# pragma warning(disable:4127) /* conditional expression is constant */ +# endif +#endif + + +#define true 1 +#define false 0 +#define XX -1 /* the universal error code */ + +/* values chosen so that the object size is approx equal to one page (4K) */ +#ifndef JSON_PARSER_STACK_SIZE +# define JSON_PARSER_STACK_SIZE 128 +#endif + +#ifndef JSON_PARSER_PARSE_BUFFER_SIZE +# define JSON_PARSER_PARSE_BUFFER_SIZE 3500 +#endif + +typedef void* (*JSON_debug_malloc_t)(size_t bytes, const char* reason); + +#ifdef JSON_PARSER_DEBUG_MALLOC +# define JSON_parser_malloc(func, bytes, reason) ((JSON_debug_malloc_t)func)(bytes, reason) +#else +# define JSON_parser_malloc(func, bytes, reason) func(bytes) +#endif + +typedef unsigned short UTF16; + +struct JSON_parser_struct { + JSON_parser_callback callback; + void* ctx; + signed char state, before_comment_state, type, escaped, comment, allow_comments, handle_floats_manually, error; + char decimal_point; + UTF16 utf16_high_surrogate; + int current_char; + int depth; + int top; + int stack_capacity; + signed char* stack; + char* parse_buffer; + size_t parse_buffer_capacity; + size_t parse_buffer_count; + signed char static_stack[JSON_PARSER_STACK_SIZE]; + char static_parse_buffer[JSON_PARSER_PARSE_BUFFER_SIZE]; + JSON_malloc_t malloc; + JSON_free_t free; +}; + +#define COUNTOF(x) (sizeof(x)/sizeof(x[0])) + +/* + Characters are mapped into these character classes. This allows for + a significant reduction in the size of the state transition table. +*/ + + + +enum classes { + C_SPACE, /* space */ + C_WHITE, /* other whitespace */ + C_LCURB, /* { */ + C_RCURB, /* } */ + C_LSQRB, /* [ */ + C_RSQRB, /* ] */ + C_COLON, /* : */ + C_COMMA, /* , */ + C_QUOTE, /* " */ + C_BACKS, /* \ */ + C_SLASH, /* / */ + C_PLUS, /* + */ + C_MINUS, /* - */ + C_POINT, /* . */ + C_ZERO , /* 0 */ + C_DIGIT, /* 123456789 */ + C_LOW_A, /* a */ + C_LOW_B, /* b */ + C_LOW_C, /* c */ + C_LOW_D, /* d */ + C_LOW_E, /* e */ + C_LOW_F, /* f */ + C_LOW_L, /* l */ + C_LOW_N, /* n */ + C_LOW_R, /* r */ + C_LOW_S, /* s */ + C_LOW_T, /* t */ + C_LOW_U, /* u */ + C_ABCDF, /* ABCDF */ + C_E, /* E */ + C_ETC, /* everything else */ + C_STAR, /* * */ + NR_CLASSES +}; + +static const signed char ascii_class[128] = { +/* + This array maps the 128 ASCII characters into character classes. + The remaining Unicode characters should be mapped to C_ETC. + Non-whitespace control characters are errors. +*/ + XX, XX, XX, XX, XX, XX, XX, XX, + XX, C_WHITE, C_WHITE, XX, XX, C_WHITE, XX, XX, + XX, XX, XX, XX, XX, XX, XX, XX, + XX, XX, XX, XX, XX, XX, XX, XX, + + C_SPACE, C_ETC, C_QUOTE, C_ETC, C_ETC, C_ETC, C_ETC, C_ETC, + C_ETC, C_ETC, C_STAR, C_PLUS, C_COMMA, C_MINUS, C_POINT, C_SLASH, + C_ZERO, C_DIGIT, C_DIGIT, C_DIGIT, C_DIGIT, C_DIGIT, C_DIGIT, C_DIGIT, + C_DIGIT, C_DIGIT, C_COLON, C_ETC, C_ETC, C_ETC, C_ETC, C_ETC, + + C_ETC, C_ABCDF, C_ABCDF, C_ABCDF, C_ABCDF, C_E, C_ABCDF, C_ETC, + C_ETC, C_ETC, C_ETC, C_ETC, C_ETC, C_ETC, C_ETC, C_ETC, + C_ETC, C_ETC, C_ETC, C_ETC, C_ETC, C_ETC, C_ETC, C_ETC, + C_ETC, C_ETC, C_ETC, C_LSQRB, C_BACKS, C_RSQRB, C_ETC, C_ETC, + + C_ETC, C_LOW_A, C_LOW_B, C_LOW_C, C_LOW_D, C_LOW_E, C_LOW_F, C_ETC, + C_ETC, C_ETC, C_ETC, C_ETC, C_LOW_L, C_ETC, C_LOW_N, C_ETC, + C_ETC, C_ETC, C_LOW_R, C_LOW_S, C_LOW_T, C_LOW_U, C_ETC, C_ETC, + C_ETC, C_ETC, C_ETC, C_LCURB, C_ETC, C_RCURB, C_ETC, C_ETC +}; + + +/* + The state codes. +*/ +enum states { + GO, /* start */ + OK, /* ok */ + OB, /* object */ + KE, /* key */ + CO, /* colon */ + VA, /* value */ + AR, /* array */ + ST, /* string */ + ESC, /* escape */ + U1, /* u1 */ + U2, /* u2 */ + U3, /* u3 */ + U4, /* u4 */ + MI, /* minus */ + ZE, /* zero */ + IT, /* integer */ + FR, /* fraction */ + E1, /* e */ + E2, /* ex */ + E3, /* exp */ + T1, /* tr */ + T2, /* tru */ + T3, /* true */ + F1, /* fa */ + F2, /* fal */ + F3, /* fals */ + F4, /* false */ + N1, /* nu */ + N2, /* nul */ + N3, /* null */ + C1, /* / */ + C2, /* / * */ + C3, /* * */ + FX, /* *.* *eE* */ + D1, /* second UTF-16 character decoding started by \ */ + D2, /* second UTF-16 character proceeded by u */ + NR_STATES +}; + +enum actions +{ + CB = -10, /* comment begin */ + CE = -11, /* comment end */ + FA = -12, /* false */ + TR = -13, /* false */ + NU = -14, /* null */ + DE = -15, /* double detected by exponent e E */ + DF = -16, /* double detected by fraction . */ + SB = -17, /* string begin */ + MX = -18, /* integer detected by minus */ + ZX = -19, /* integer detected by zero */ + IX = -20, /* integer detected by 1-9 */ + EX = -21, /* next char is escaped */ + UC = -22 /* Unicode character read */ +}; + + +static const signed char state_transition_table[NR_STATES][NR_CLASSES] = { +/* + The state transition table takes the current state and the current symbol, + and returns either a new state or an action. An action is represented as a + negative number. A JSON text is accepted if at the end of the text the + state is OK and if the mode is MODE_DONE. + + white 1-9 ABCDF etc + space | { } [ ] : , " \ / + - . 0 | a b c d e f l n r s t u | E | * */ +/*start GO*/ {GO,GO,-6,XX,-5,XX,XX,XX,XX,XX,CB,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX}, +/*ok OK*/ {OK,OK,XX,-8,XX,-7,XX,-3,XX,XX,CB,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX}, +/*object OB*/ {OB,OB,XX,-9,XX,XX,XX,XX,SB,XX,CB,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX}, +/*key KE*/ {KE,KE,XX,XX,XX,XX,XX,XX,SB,XX,CB,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX}, +/*colon CO*/ {CO,CO,XX,XX,XX,XX,-2,XX,XX,XX,CB,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX}, +/*value VA*/ {VA,VA,-6,XX,-5,XX,XX,XX,SB,XX,CB,XX,MX,XX,ZX,IX,XX,XX,XX,XX,XX,FA,XX,NU,XX,XX,TR,XX,XX,XX,XX,XX}, +/*array AR*/ {AR,AR,-6,XX,-5,-7,XX,XX,SB,XX,CB,XX,MX,XX,ZX,IX,XX,XX,XX,XX,XX,FA,XX,NU,XX,XX,TR,XX,XX,XX,XX,XX}, +/*string ST*/ {ST,XX,ST,ST,ST,ST,ST,ST,-4,EX,ST,ST,ST,ST,ST,ST,ST,ST,ST,ST,ST,ST,ST,ST,ST,ST,ST,ST,ST,ST,ST,ST}, +/*escape ES*/ {XX,XX,XX,XX,XX,XX,XX,XX,ST,ST,ST,XX,XX,XX,XX,XX,XX,ST,XX,XX,XX,ST,XX,ST,ST,XX,ST,U1,XX,XX,XX,XX}, +/*u1 U1*/ {XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,U2,U2,U2,U2,U2,U2,U2,U2,XX,XX,XX,XX,XX,XX,U2,U2,XX,XX}, +/*u2 U2*/ {XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,U3,U3,U3,U3,U3,U3,U3,U3,XX,XX,XX,XX,XX,XX,U3,U3,XX,XX}, +/*u3 U3*/ {XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,U4,U4,U4,U4,U4,U4,U4,U4,XX,XX,XX,XX,XX,XX,U4,U4,XX,XX}, +/*u4 U4*/ {XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,UC,UC,UC,UC,UC,UC,UC,UC,XX,XX,XX,XX,XX,XX,UC,UC,XX,XX}, +/*minus MI*/ {XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,ZE,IT,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX}, +/*zero ZE*/ {OK,OK,XX,-8,XX,-7,XX,-3,XX,XX,CB,XX,XX,DF,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX}, +/*int IT*/ {OK,OK,XX,-8,XX,-7,XX,-3,XX,XX,CB,XX,XX,DF,IT,IT,XX,XX,XX,XX,DE,XX,XX,XX,XX,XX,XX,XX,XX,DE,XX,XX}, +/*frac FR*/ {OK,OK,XX,-8,XX,-7,XX,-3,XX,XX,CB,XX,XX,XX,FR,FR,XX,XX,XX,XX,E1,XX,XX,XX,XX,XX,XX,XX,XX,E1,XX,XX}, +/*e E1*/ {XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,E2,E2,XX,E3,E3,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX}, +/*ex E2*/ {XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,E3,E3,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX}, +/*exp E3*/ {OK,OK,XX,-8,XX,-7,XX,-3,XX,XX,XX,XX,XX,XX,E3,E3,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX}, +/*tr T1*/ {XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,T2,XX,XX,XX,XX,XX,XX,XX}, +/*tru T2*/ {XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,T3,XX,XX,XX,XX}, +/*true T3*/ {XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,CB,XX,XX,XX,XX,XX,XX,XX,XX,XX,OK,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX}, +/*fa F1*/ {XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,F2,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX}, +/*fal F2*/ {XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,F3,XX,XX,XX,XX,XX,XX,XX,XX,XX}, +/*fals F3*/ {XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,F4,XX,XX,XX,XX,XX,XX}, +/*false F4*/ {XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,CB,XX,XX,XX,XX,XX,XX,XX,XX,XX,OK,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX}, +/*nu N1*/ {XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,N2,XX,XX,XX,XX}, +/*nul N2*/ {XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,N3,XX,XX,XX,XX,XX,XX,XX,XX,XX}, +/*null N3*/ {XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,CB,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,OK,XX,XX,XX,XX,XX,XX,XX,XX,XX}, +/*/ C1*/ {XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,C2}, +/*/star C2*/ {C2,C2,C2,C2,C2,C2,C2,C2,C2,C2,C2,C2,C2,C2,C2,C2,C2,C2,C2,C2,C2,C2,C2,C2,C2,C2,C2,C2,C2,C2,C2,C3}, +/** C3*/ {C2,C2,C2,C2,C2,C2,C2,C2,C2,C2,CE,C2,C2,C2,C2,C2,C2,C2,C2,C2,C2,C2,C2,C2,C2,C2,C2,C2,C2,C2,C2,C3}, +/*_. FX*/ {OK,OK,XX,-8,XX,-7,XX,-3,XX,XX,XX,XX,XX,XX,FR,FR,XX,XX,XX,XX,E1,XX,XX,XX,XX,XX,XX,XX,XX,E1,XX,XX}, +/*\ D1*/ {XX,XX,XX,XX,XX,XX,XX,XX,XX,D2,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX}, +/*\ D2*/ {XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,XX,U1,XX,XX,XX,XX}, +}; + + +/* + These modes can be pushed on the stack. +*/ +enum modes { + MODE_ARRAY = 1, + MODE_DONE = 2, + MODE_KEY = 3, + MODE_OBJECT = 4 +}; + +static void set_error(JSON_parser jc) +{ + switch (jc->state) { + case GO: + switch (jc->current_char) { + case '{': case '}': case '[': case ']': + jc->error = JSON_E_UNBALANCED_COLLECTION; + break; + default: + jc->error = JSON_E_INVALID_CHAR; + break; + } + break; + case OB: + jc->error = JSON_E_EXPECTED_KEY; + break; + case AR: + jc->error = JSON_E_UNBALANCED_COLLECTION; + break; + case CO: + jc->error = JSON_E_EXPECTED_COLON; + break; + case KE: + jc->error = JSON_E_EXPECTED_KEY; + break; + /* \uXXXX\uYYYY */ + case U1: case U2: case U3: case U4: case D1: case D2: + jc->error = JSON_E_INVALID_UNICODE_SEQUENCE; + break; + /* true, false, null */ + case T1: case T2: case T3: case F1: case F2: case F3: case F4: case N1: case N2: case N3: + jc->error = JSON_E_INVALID_KEYWORD; + break; + /* minus, integer, fraction, exponent */ + case MI: case ZE: case IT: case FR: case E1: case E2: case E3: + jc->error = JSON_E_INVALID_NUMBER; + break; + default: + jc->error = JSON_E_INVALID_CHAR; + break; + } +} + +static int +push(JSON_parser jc, int mode) +{ +/* + Push a mode onto the stack. Return false if there is overflow. +*/ + assert(jc->top <= jc->stack_capacity); + + if (jc->depth < 0) { + if (jc->top == jc->stack_capacity) { + const size_t bytes_to_copy = jc->stack_capacity * sizeof(jc->stack[0]); + const size_t new_capacity = jc->stack_capacity * 2; + const size_t bytes_to_allocate = new_capacity * sizeof(jc->stack[0]); + void* mem = JSON_parser_malloc(jc->malloc, bytes_to_allocate, "stack"); + if (!mem) { + jc->error = JSON_E_OUT_OF_MEMORY; + return false; + } + jc->stack_capacity = (int)new_capacity; + memcpy(mem, jc->stack, bytes_to_copy); + if (jc->stack != &jc->static_stack[0]) { + jc->free(jc->stack); + } + jc->stack = (signed char*)mem; + } + } else { + if (jc->top == jc->depth) { + jc->error = JSON_E_NESTING_DEPTH_REACHED; + return false; + } + } + jc->stack[++jc->top] = (signed char)mode; + return true; +} + + +static int +pop(JSON_parser jc, int mode) +{ +/* + Pop the stack, assuring that the current mode matches the expectation. + Return false if there is underflow or if the modes mismatch. +*/ + if (jc->top < 0 || jc->stack[jc->top] != mode) { + return false; + } + jc->top -= 1; + return true; +} + + +#define parse_buffer_clear(jc) \ + do {\ + jc->parse_buffer_count = 0;\ + jc->parse_buffer[0] = 0;\ + } while (0) + +#define parse_buffer_pop_back_char(jc)\ + do {\ + assert(jc->parse_buffer_count >= 1);\ + --jc->parse_buffer_count;\ + jc->parse_buffer[jc->parse_buffer_count] = 0;\ + } while (0) + + + +void delete_JSON_parser(JSON_parser jc) +{ + if (jc) { + if (jc->stack != &jc->static_stack[0]) { + jc->free((void*)jc->stack); + } + if (jc->parse_buffer != &jc->static_parse_buffer[0]) { + jc->free((void*)jc->parse_buffer); + } + jc->free((void*)jc); + } +} + +int JSON_parser_reset(JSON_parser jc) +{ + if (NULL == jc) { + return false; + } + + jc->state = GO; + jc->top = -1; + + /* parser has been used previously? */ + if (NULL == jc->parse_buffer) { + + /* Do we want non-bound stack? */ + if (jc->depth > 0) { + jc->stack_capacity = jc->depth; + if (jc->depth <= (int)COUNTOF(jc->static_stack)) { + jc->stack = &jc->static_stack[0]; + } else { + const size_t bytes_to_alloc = jc->stack_capacity * sizeof(jc->stack[0]); + jc->stack = (signed char*)JSON_parser_malloc(jc->malloc, bytes_to_alloc, "stack"); + if (jc->stack == NULL) { + return false; + } + } + } else { + jc->stack_capacity = (int)COUNTOF(jc->static_stack); + jc->depth = -1; + jc->stack = &jc->static_stack[0]; + } + + /* set up the parse buffer */ + jc->parse_buffer = &jc->static_parse_buffer[0]; + jc->parse_buffer_capacity = COUNTOF(jc->static_parse_buffer); + } + + /* set parser to start */ + push(jc, MODE_DONE); + parse_buffer_clear(jc); + + return true; +} + +JSON_parser +new_JSON_parser(JSON_config const * config) +{ +/* + new_JSON_parser starts the checking process by constructing a JSON_parser + object. It takes a depth parameter that restricts the level of maximum + nesting. + + To continue the process, call JSON_parser_char for each character in the + JSON text, and then call JSON_parser_done to obtain the final result. + These functions are fully reentrant. +*/ + + int use_std_malloc = false; + JSON_config default_config; + JSON_parser jc; + JSON_malloc_t alloc; + + /* set to default configuration if none was provided */ + if (NULL == config) { + /* initialize configuration */ + init_JSON_config(&default_config); + config = &default_config; + } + + /* use std malloc if either the allocator or deallocator function isn't set */ + use_std_malloc = NULL == config->malloc || NULL == config->free; + + alloc = use_std_malloc ? malloc : config->malloc; + + jc = (JSON_parser)JSON_parser_malloc(alloc, sizeof(*jc), "parser"); + + if (NULL == jc) { + return NULL; + } + + /* configure the parser */ + memset(jc, 0, sizeof(*jc)); + jc->malloc = alloc; + jc->free = use_std_malloc ? free : config->free; + jc->callback = config->callback; + jc->ctx = config->callback_ctx; + jc->allow_comments = (signed char)(config->allow_comments != 0); + jc->handle_floats_manually = (signed char)(config->handle_floats_manually != 0); + jc->decimal_point = *localeconv()->decimal_point; + /* We need to be able to push at least one object */ + jc->depth = config->depth == 0 ? 1 : config->depth; + + /* reset the parser */ + if (!JSON_parser_reset(jc)) { + jc->free(jc); + return NULL; + } + + return jc; +} + +static int parse_buffer_grow(JSON_parser jc) +{ + const size_t bytes_to_copy = jc->parse_buffer_count * sizeof(jc->parse_buffer[0]); + const size_t new_capacity = jc->parse_buffer_capacity * 2; + const size_t bytes_to_allocate = new_capacity * sizeof(jc->parse_buffer[0]); + void* mem = JSON_parser_malloc(jc->malloc, bytes_to_allocate, "parse buffer"); + + if (mem == NULL) { + jc->error = JSON_E_OUT_OF_MEMORY; + return false; + } + + assert(new_capacity > 0); + memcpy(mem, jc->parse_buffer, bytes_to_copy); + + if (jc->parse_buffer != &jc->static_parse_buffer[0]) { + jc->free(jc->parse_buffer); + } + + jc->parse_buffer = (char*)mem; + jc->parse_buffer_capacity = new_capacity; + + return true; +} + +static int parse_buffer_reserve_for(JSON_parser jc, unsigned chars) +{ + while (jc->parse_buffer_count + chars + 1 > jc->parse_buffer_capacity) { + if (!parse_buffer_grow(jc)) { + assert(jc->error == JSON_E_OUT_OF_MEMORY); + return false; + } + } + + return true; +} + +#define parse_buffer_has_space_for(jc, count) \ + (jc->parse_buffer_count + (count) + 1 <= jc->parse_buffer_capacity) + +#define parse_buffer_push_back_char(jc, c)\ + do {\ + assert(parse_buffer_has_space_for(jc, 1)); \ + jc->parse_buffer[jc->parse_buffer_count++] = c;\ + jc->parse_buffer[jc->parse_buffer_count] = 0;\ + } while (0) + +#define assert_is_non_container_type(jc) \ + assert( \ + jc->type == JSON_T_NULL || \ + jc->type == JSON_T_FALSE || \ + jc->type == JSON_T_TRUE || \ + jc->type == JSON_T_FLOAT || \ + jc->type == JSON_T_INTEGER || \ + jc->type == JSON_T_STRING) + + +static int parse_parse_buffer(JSON_parser jc) +{ + if (jc->callback) { + JSON_value value, *arg = NULL; + + if (jc->type != JSON_T_NONE) { + assert_is_non_container_type(jc); + + switch(jc->type) { + case JSON_T_FLOAT: + arg = &value; + if (jc->handle_floats_manually) { + value.vu.str.value = jc->parse_buffer; + value.vu.str.length = jc->parse_buffer_count; + } else { + /* not checking with end pointer b/c there may be trailing ws */ + value.vu.float_value = strtod(jc->parse_buffer, NULL); + } + break; + case JSON_T_INTEGER: + arg = &value; + sscanf(jc->parse_buffer, JSON_PARSER_INTEGER_SSCANF_TOKEN, &value.vu.integer_value); + break; + case JSON_T_STRING: + arg = &value; + value.vu.str.value = jc->parse_buffer; + value.vu.str.length = jc->parse_buffer_count; + break; + } + + if (!(*jc->callback)(jc->ctx, jc->type, arg)) { + return false; + } + } + } + + parse_buffer_clear(jc); + + return true; +} + +#define IS_HIGH_SURROGATE(uc) (((uc) & 0xFC00) == 0xD800) +#define IS_LOW_SURROGATE(uc) (((uc) & 0xFC00) == 0xDC00) +#define DECODE_SURROGATE_PAIR(hi,lo) ((((hi) & 0x3FF) << 10) + ((lo) & 0x3FF) + 0x10000) +static const unsigned char utf8_lead_bits[4] = { 0x00, 0xC0, 0xE0, 0xF0 }; + +static int decode_unicode_char(JSON_parser jc) +{ + int i; + unsigned uc = 0; + char* p; + int trail_bytes; + + assert(jc->parse_buffer_count >= 6); + + p = &jc->parse_buffer[jc->parse_buffer_count - 4]; + + for (i = 12; i >= 0; i -= 4, ++p) { + unsigned x = *p; + + if (x >= 'a') { + x -= ('a' - 10); + } else if (x >= 'A') { + x -= ('A' - 10); + } else { + x &= ~0x30u; + } + + assert(x < 16); + + uc |= x << i; + } + + /* clear UTF-16 char from buffer */ + jc->parse_buffer_count -= 6; + jc->parse_buffer[jc->parse_buffer_count] = 0; + + if (uc == 0xffff || uc == 0xfffe) { + return false; + } + + /* attempt decoding ... */ + if (jc->utf16_high_surrogate) { + if (IS_LOW_SURROGATE(uc)) { + uc = DECODE_SURROGATE_PAIR(jc->utf16_high_surrogate, uc); + trail_bytes = 3; + jc->utf16_high_surrogate = 0; + } else { + /* high surrogate without a following low surrogate */ + return false; + } + } else { + if (uc < 0x80) { + trail_bytes = 0; + } else if (uc < 0x800) { + trail_bytes = 1; + } else if (IS_HIGH_SURROGATE(uc)) { + /* save the high surrogate and wait for the low surrogate */ + jc->utf16_high_surrogate = (UTF16)uc; + return true; + } else if (IS_LOW_SURROGATE(uc)) { + /* low surrogate without a preceding high surrogate */ + return false; + } else { + trail_bytes = 2; + } + } + + jc->parse_buffer[jc->parse_buffer_count++] = (char) ((uc >> (trail_bytes * 6)) | utf8_lead_bits[trail_bytes]); + + for (i = trail_bytes * 6 - 6; i >= 0; i -= 6) { + jc->parse_buffer[jc->parse_buffer_count++] = (char) (((uc >> i) & 0x3F) | 0x80); + } + + jc->parse_buffer[jc->parse_buffer_count] = 0; + + return true; +} + +static int add_escaped_char_to_parse_buffer(JSON_parser jc, int next_char) +{ + assert(parse_buffer_has_space_for(jc, 1)); + + jc->escaped = 0; + /* remove the backslash */ + parse_buffer_pop_back_char(jc); + switch(next_char) { + case 'b': + parse_buffer_push_back_char(jc, '\b'); + break; + case 'f': + parse_buffer_push_back_char(jc, '\f'); + break; + case 'n': + parse_buffer_push_back_char(jc, '\n'); + break; + case 'r': + parse_buffer_push_back_char(jc, '\r'); + break; + case 't': + parse_buffer_push_back_char(jc, '\t'); + break; + case '"': + parse_buffer_push_back_char(jc, '"'); + break; + case '\\': + parse_buffer_push_back_char(jc, '\\'); + break; + case '/': + parse_buffer_push_back_char(jc, '/'); + break; + case 'u': + parse_buffer_push_back_char(jc, '\\'); + parse_buffer_push_back_char(jc, 'u'); + break; + default: + return false; + } + + return true; +} + +static int add_char_to_parse_buffer(JSON_parser jc, int next_char, int next_class) +{ + if (!parse_buffer_reserve_for(jc, 1)) { + assert(JSON_E_OUT_OF_MEMORY == jc->error); + return false; + } + + if (jc->escaped) { + if (!add_escaped_char_to_parse_buffer(jc, next_char)) { + jc->error = JSON_E_INVALID_ESCAPE_SEQUENCE; + return false; + } + } else if (!jc->comment) { + if ((jc->type != JSON_T_NONE) | !((next_class == C_SPACE) | (next_class == C_WHITE)) /* non-white-space */) { + parse_buffer_push_back_char(jc, (char)next_char); + } + } + + return true; +} + +#define assert_type_isnt_string_null_or_bool(jc) \ + assert(jc->type != JSON_T_FALSE); \ + assert(jc->type != JSON_T_TRUE); \ + assert(jc->type != JSON_T_NULL); \ + assert(jc->type != JSON_T_STRING) + + +int +JSON_parser_char(JSON_parser jc, int next_char) +{ +/* + After calling new_JSON_parser, call this function for each character (or + partial character) in your JSON text. It can accept UTF-8, UTF-16, or + UTF-32. It returns true if things are looking ok so far. If it rejects the + text, it returns false. +*/ + int next_class, next_state; + +/* + Store the current char for error handling +*/ + jc->current_char = next_char; + +/* + Determine the character's class. +*/ + if (next_char < 0) { + jc->error = JSON_E_INVALID_CHAR; + return false; + } + if (next_char >= 128) { + next_class = C_ETC; + } else { + next_class = ascii_class[next_char]; + if (next_class <= XX) { + set_error(jc); + return false; + } + } + + if (!add_char_to_parse_buffer(jc, next_char, next_class)) { + return false; + } + +/* + Get the next state from the state transition table. +*/ + next_state = state_transition_table[jc->state][next_class]; + if (next_state >= 0) { +/* + Change the state. +*/ + jc->state = (signed char)next_state; + } else { +/* + Or perform one of the actions. +*/ + switch (next_state) { +/* Unicode character */ + case UC: + if(!decode_unicode_char(jc)) { + jc->error = JSON_E_INVALID_UNICODE_SEQUENCE; + return false; + } + /* check if we need to read a second UTF-16 char */ + if (jc->utf16_high_surrogate) { + jc->state = D1; + } else { + jc->state = ST; + } + break; +/* escaped char */ + case EX: + jc->escaped = 1; + jc->state = ESC; + break; +/* integer detected by minus */ + case MX: + jc->type = JSON_T_INTEGER; + jc->state = MI; + break; +/* integer detected by zero */ + case ZX: + jc->type = JSON_T_INTEGER; + jc->state = ZE; + break; +/* integer detected by 1-9 */ + case IX: + jc->type = JSON_T_INTEGER; + jc->state = IT; + break; + +/* floating point number detected by exponent*/ + case DE: + assert_type_isnt_string_null_or_bool(jc); + jc->type = JSON_T_FLOAT; + jc->state = E1; + break; + +/* floating point number detected by fraction */ + case DF: + assert_type_isnt_string_null_or_bool(jc); + if (!jc->handle_floats_manually) { +/* + Some versions of strtod (which underlies sscanf) don't support converting + C-locale formated floating point values. +*/ + assert(jc->parse_buffer[jc->parse_buffer_count-1] == '.'); + jc->parse_buffer[jc->parse_buffer_count-1] = jc->decimal_point; + } + jc->type = JSON_T_FLOAT; + jc->state = FX; + break; +/* string begin " */ + case SB: + parse_buffer_clear(jc); + assert(jc->type == JSON_T_NONE); + jc->type = JSON_T_STRING; + jc->state = ST; + break; + +/* n */ + case NU: + assert(jc->type == JSON_T_NONE); + jc->type = JSON_T_NULL; + jc->state = N1; + break; +/* f */ + case FA: + assert(jc->type == JSON_T_NONE); + jc->type = JSON_T_FALSE; + jc->state = F1; + break; +/* t */ + case TR: + assert(jc->type == JSON_T_NONE); + jc->type = JSON_T_TRUE; + jc->state = T1; + break; + +/* closing comment */ + case CE: + jc->comment = 0; + assert(jc->parse_buffer_count == 0); + assert(jc->type == JSON_T_NONE); + jc->state = jc->before_comment_state; + break; + +/* opening comment */ + case CB: + if (!jc->allow_comments) { + return false; + } + parse_buffer_pop_back_char(jc); + if (!parse_parse_buffer(jc)) { + return false; + } + assert(jc->parse_buffer_count == 0); + assert(jc->type != JSON_T_STRING); + switch (jc->stack[jc->top]) { + case MODE_ARRAY: + case MODE_OBJECT: + switch(jc->state) { + case VA: + case AR: + jc->before_comment_state = jc->state; + break; + default: + jc->before_comment_state = OK; + break; + } + break; + default: + jc->before_comment_state = jc->state; + break; + } + jc->type = JSON_T_NONE; + jc->state = C1; + jc->comment = 1; + break; +/* empty } */ + case -9: + parse_buffer_clear(jc); + if (jc->callback && !(*jc->callback)(jc->ctx, JSON_T_OBJECT_END, NULL)) { + return false; + } + if (!pop(jc, MODE_KEY)) { + return false; + } + jc->state = OK; + break; + +/* } */ case -8: + parse_buffer_pop_back_char(jc); + if (!parse_parse_buffer(jc)) { + return false; + } + if (jc->callback && !(*jc->callback)(jc->ctx, JSON_T_OBJECT_END, NULL)) { + return false; + } + if (!pop(jc, MODE_OBJECT)) { + jc->error = JSON_E_UNBALANCED_COLLECTION; + return false; + } + jc->type = JSON_T_NONE; + jc->state = OK; + break; + +/* ] */ case -7: + parse_buffer_pop_back_char(jc); + if (!parse_parse_buffer(jc)) { + return false; + } + if (jc->callback && !(*jc->callback)(jc->ctx, JSON_T_ARRAY_END, NULL)) { + return false; + } + if (!pop(jc, MODE_ARRAY)) { + jc->error = JSON_E_UNBALANCED_COLLECTION; + return false; + } + + jc->type = JSON_T_NONE; + jc->state = OK; + break; + +/* { */ case -6: + parse_buffer_pop_back_char(jc); + if (jc->callback && !(*jc->callback)(jc->ctx, JSON_T_OBJECT_BEGIN, NULL)) { + return false; + } + if (!push(jc, MODE_KEY)) { + return false; + } + assert(jc->type == JSON_T_NONE); + jc->state = OB; + break; + +/* [ */ case -5: + parse_buffer_pop_back_char(jc); + if (jc->callback && !(*jc->callback)(jc->ctx, JSON_T_ARRAY_BEGIN, NULL)) { + return false; + } + if (!push(jc, MODE_ARRAY)) { + return false; + } + assert(jc->type == JSON_T_NONE); + jc->state = AR; + break; + +/* string end " */ case -4: + parse_buffer_pop_back_char(jc); + switch (jc->stack[jc->top]) { + case MODE_KEY: + assert(jc->type == JSON_T_STRING); + jc->type = JSON_T_NONE; + jc->state = CO; + + if (jc->callback) { + JSON_value value; + value.vu.str.value = jc->parse_buffer; + value.vu.str.length = jc->parse_buffer_count; + if (!(*jc->callback)(jc->ctx, JSON_T_KEY, &value)) { + return false; + } + } + parse_buffer_clear(jc); + break; + case MODE_ARRAY: + case MODE_OBJECT: + assert(jc->type == JSON_T_STRING); + if (!parse_parse_buffer(jc)) { + return false; + } + jc->type = JSON_T_NONE; + jc->state = OK; + break; + default: + return false; + } + break; + +/* , */ case -3: + parse_buffer_pop_back_char(jc); + if (!parse_parse_buffer(jc)) { + return false; + } + switch (jc->stack[jc->top]) { + case MODE_OBJECT: +/* + A comma causes a flip from object mode to key mode. +*/ + if (!pop(jc, MODE_OBJECT) || !push(jc, MODE_KEY)) { + return false; + } + assert(jc->type != JSON_T_STRING); + jc->type = JSON_T_NONE; + jc->state = KE; + break; + case MODE_ARRAY: + assert(jc->type != JSON_T_STRING); + jc->type = JSON_T_NONE; + jc->state = VA; + break; + default: + return false; + } + break; + +/* : */ case -2: +/* + A colon causes a flip from key mode to object mode. +*/ + parse_buffer_pop_back_char(jc); + if (!pop(jc, MODE_KEY) || !push(jc, MODE_OBJECT)) { + return false; + } + assert(jc->type == JSON_T_NONE); + jc->state = VA; + break; +/* + Bad action. +*/ + default: + set_error(jc); + return false; + } + } + return true; +} + +int +JSON_parser_done(JSON_parser jc) +{ + if ((jc->state == OK || jc->state == GO) && pop(jc, MODE_DONE)) + { + return true; + } + + jc->error = JSON_E_UNBALANCED_COLLECTION; + return false; +} + + +int JSON_parser_is_legal_white_space_string(const char* s) +{ + int c, char_class; + + if (s == NULL) { + return false; + } + + for (; *s; ++s) { + c = *s; + + if (c < 0 || c >= 128) { + return false; + } + + char_class = ascii_class[c]; + + if (char_class != C_SPACE && char_class != C_WHITE) { + return false; + } + } + + return true; +} + +int JSON_parser_get_last_error(JSON_parser jc) +{ + return jc->error; +} + + +void init_JSON_config(JSON_config* config) +{ + if (config) { + memset(config, 0, sizeof(*config)); + + config->depth = JSON_PARSER_STACK_SIZE - 1; + config->malloc = malloc; + config->free = free; + } +} + +#undef XX +#undef COUNTOF +#undef parse_buffer_clear +#undef parse_buffer_pop_back_char +/* end file parser/JSON_parser.c */ +/* begin file ./cson.c */ +#include +#include /* malloc()/free() */ +#include +#include + +#ifdef _MSC_VER +# if _MSC_VER >= 1400 /* Visual Studio 2005 and up */ +# pragma warning( push ) +# pragma warning(disable:4996) /* unsecure sscanf (but snscanf() isn't in c89) */ +# pragma warning(disable:4244) /* complaining about data loss due + to integer precision in the + sqlite3 utf decoding routines */ +# endif +#endif + +#if 1 +#include +#define MARKER if(1) printf("MARKER: %s:%d:%s():\t",__FILE__,__LINE__,__func__); if(1) printf +#else +static void noop_printf(char const * fmt, ...) {} +#define MARKER if(0) printf +#endif + +#if defined(__cplusplus) +extern "C" { +#endif + + + +/** + This type holds the "vtbl" for type-specific operations when + working with cson_value objects. + + All cson_values of a given logical type share a pointer to a single + library-internal instance of this class. +*/ +struct cson_value_api +{ + /** + The logical JavaScript/JSON type associated with + this object. + */ + const cson_type_id typeID; + /** + Must free any memory associated with self, + but not free self. If self is NULL then + this function must do nothing. + */ + void (*cleanup)( cson_value * self ); + /** + POSSIBLE TODOs: + + // Deep copy. + int (*clone)( cson_value const * self, cson_value ** tgt ); + + // Using JS semantics for true/value + char (*bool_value)( cson_value const * self ); + + // memcmp() return value semantics + int (*compare)( cson_value const * self, cson_value const * other ); + */ +}; + +typedef struct cson_value_api cson_value_api; + +/** + Empty-initialized cson_value_api object. +*/ +#define cson_value_api_empty_m { \ + CSON_TYPE_UNDEF/*typeID*/, \ + NULL/*cleanup*/\ + } +/** + Empty-initialized cson_value_api object. +*/ +/*static const cson_value_api cson_value_api_empty = cson_value_api_empty_m;*/ + + +typedef unsigned int cson_counter_t; +struct cson_value +{ + /** The "vtbl" of type-specific operations. All instances + of a given logical value type share a single api instance. + + Results are undefined if this value is NULL. + */ + cson_value_api const * api; + + /** The raw value. Its interpretation depends on the value of the + api member. Some value types require dynamically-allocated + memory, so one must always call cson_value_free() to destroy a + value when it is no longer needed. For stack-allocated values + (which client could SHOULD NOT USE unless they are intimately + familiar with the memory management rules and don't mind an + occasional leak or crash), use cson_value_clean() instead of + cson_value_free(). + */ + void * value; + + /** + We use this to allow us to store cson_value instances in + multiple containers or multiple times within a single container + (provided no cycles are introduced). + + Notes about the rc implementation: + + - The refcount is for the cson_value instance itself, not its + value pointer. + + - Instances start out with a refcount of 0 (not 1). Adding them + to a container will increase the refcount. Cleaning up the container + will decrement the count. + + - cson_value_free() decrements the refcount (if it is not already + 0) and cleans/frees the value only when the refcount is 0. + + - Some places in the internals add an "extra" reference to + objects to avoid a premature deletion. Don't try this at home. + */ + cson_counter_t refcount; +}; + + +/** + Empty-initialized cson_value object. +*/ +const cson_parse_opt cson_parse_opt_empty = cson_parse_opt_empty_m; +const cson_output_opt cson_output_opt_empty = cson_output_opt_empty_m; +const cson_object_iterator cson_object_iterator_empty = cson_object_iterator_empty_m; +const cson_buffer cson_buffer_empty = cson_buffer_empty_m; +const cson_parse_info cson_parse_info_empty = cson_parse_info_empty_m; + +static void cson_value_destroy_zero_it( cson_value * self ); +static void cson_value_destroy_object( cson_value * self ); +/** + If self is-a array then this function destroys its contents, + else this function does nothing. +*/ +static void cson_value_destroy_array( cson_value * self ); + +static const cson_value_api cson_value_api_null = { CSON_TYPE_NULL, cson_value_destroy_zero_it }; +static const cson_value_api cson_value_api_undef = { CSON_TYPE_UNDEF, cson_value_destroy_zero_it }; +static const cson_value_api cson_value_api_bool = { CSON_TYPE_BOOL, cson_value_destroy_zero_it }; +static const cson_value_api cson_value_api_integer = { CSON_TYPE_INTEGER, cson_value_destroy_zero_it }; +static const cson_value_api cson_value_api_double = { CSON_TYPE_DOUBLE, cson_value_destroy_zero_it }; +static const cson_value_api cson_value_api_string = { CSON_TYPE_STRING, cson_value_destroy_zero_it }; +static const cson_value_api cson_value_api_array = { CSON_TYPE_ARRAY, cson_value_destroy_array }; +static const cson_value_api cson_value_api_object = { CSON_TYPE_OBJECT, cson_value_destroy_object }; + +static const cson_value cson_value_undef = { &cson_value_api_undef, NULL, 0 }; +static const cson_value cson_value_integer_empty = { &cson_value_api_integer, NULL, 0 }; +static const cson_value cson_value_double_empty = { &cson_value_api_double, NULL, 0 }; +static const cson_value cson_value_string_empty = { &cson_value_api_string, NULL, 0 }; +static const cson_value cson_value_array_empty = { &cson_value_api_array, NULL, 0 }; +static const cson_value cson_value_object_empty = { &cson_value_api_object, NULL, 0 }; + +/** + Strings are allocated as an instances of this class with N+1 + trailing bytes, where N is the length of the string being + allocated. To convert a cson_string to c-string we simply increment + the cson_string pointer. To do the opposite we use (cstr - + sizeof(cson_string)). Zero-length strings are a special case + handled by a couple of the cson_string functions. +*/ +struct cson_string +{ + unsigned int length; +}; +#define cson_string_empty_m {0/*length*/} +static const cson_string cson_string_empty = cson_string_empty_m; + + +/** + Assumes V is a (cson_value*) ans V->value is a (T*). Returns + V->value cast to a (T*). +*/ +#define CSON_CAST(T,V) ((T*)((V)->value)) +/** + Assumes V is a pointer to memory which is allocated as part of a + cson_value instance (the bytes immediately after that part). + Returns a pointer a a cson_value by subtracting sizeof(cson_value) + from that address and casting it to a (cson_value*) +*/ +#define CSON_VCAST(V) ((cson_value *)(((unsigned char *)(V))-sizeof(cson_value))) + +/** + CSON_INT(V) assumes that V is a (cson_value*) of type + CSON_TYPE_INTEGER. This macro returns a (cson_int_t*) representing + its value (how that is stored depends on whether we are running in + 32- or 64-bit mode). + */ +#if CSON_VOID_PTR_IS_BIG +# define CSON_INT(V) ((cson_int_t*)(&((V)->value))) +#else +# define CSON_INT(V) ((cson_int_t*)(V)->value) +#endif + +#define CSON_DBL(V) CSON_CAST(cson_double_t,(V)) +#define CSON_STR(V) CSON_CAST(cson_string,(V)) +#define CSON_OBJ(V) CSON_CAST(cson_object,(V)) +#define CSON_ARRAY(V) CSON_CAST(cson_array,(V)) + +/** + Holds special shared "constant" (though they are non-const) + values. +*/ +static struct CSON_EMPTY_HOLDER_ +{ + char trueValue; + cson_string stringValue; +} CSON_EMPTY_HOLDER = { + 1/*trueValue*/, + cson_string_empty_m +}; + +/** + Indexes into the CSON_SPECIAL_VALUES array. + + If this enum changes in any way, + makes damned sure that CSON_SPECIAL_VALUES is updated + to match!!! +*/ +enum CSON_INTERNAL_VALUES { + + CSON_VAL_UNDEF = 0, + CSON_VAL_NULL = 1, + CSON_VAL_TRUE = 2, + CSON_VAL_FALSE = 3, + CSON_VAL_INT_0 = 4, + CSON_VAL_DBL_0 = 5, + CSON_VAL_STR_EMPTY = 6, + CSON_INTERNAL_VALUES_LENGTH +}; + +/** + Some "special" shared cson_value instances. + + These values MUST be initialized in the order specified + by the CSON_INTERNAL_VALUES enum. + + Note that they are not const because they are used as + shared-allocation objects in non-const contexts. However, the + public API provides no way to modifying them, and clients who + modify values directly are subject to The Wrath of Undefined + Behaviour. +*/ +static cson_value CSON_SPECIAL_VALUES[] = { +{ &cson_value_api_undef, NULL, 0 }, /* UNDEF */ +{ &cson_value_api_null, NULL, 0 }, /* NULL */ +{ &cson_value_api_bool, &CSON_EMPTY_HOLDER.trueValue, 0 }, /* TRUE */ +{ &cson_value_api_bool, NULL, 0 }, /* FALSE */ +{ &cson_value_api_integer, NULL, 0 }, /* INT_0 */ +{ &cson_value_api_double, NULL, 0 }, /* DBL_0 */ +{ &cson_value_api_string, &CSON_EMPTY_HOLDER.stringValue, 0 }, /* STR_EMPTY */ +{ NULL, NULL, 0 } +}; + + +/** + Returns non-0 (true) if m is one of our special + "built-in" values, e.g. from CSON_SPECIAL_VALUES and some + "empty" values. + + If this returns true, m MUST NOT be free()d! + */ +static char cson_value_is_builtin( void const * m ) +{ + if((m >= (void const *)&CSON_EMPTY_HOLDER) + && ( m < (void const *)(&CSON_EMPTY_HOLDER+1))) + return 1; + else return + ((m >= (void const *)&CSON_SPECIAL_VALUES[0]) + && ( m < (void const *)&CSON_SPECIAL_VALUES[CSON_INTERNAL_VALUES_LENGTH]) ) + ? 1 + : 0; +} + +char const * cson_rc_string(int rc) +{ + if(0 == rc) return "OK"; +#define CHECK(N) else if(cson_rc.N == rc ) return #N + CHECK(OK); + CHECK(ArgError); + CHECK(RangeError); + CHECK(TypeError); + CHECK(IOError); + CHECK(AllocError); + CHECK(NYIError); + CHECK(InternalError); + CHECK(UnsupportedError); + CHECK(NotFoundError); + CHECK(UnknownError); + CHECK(Parse_INVALID_CHAR); + CHECK(Parse_INVALID_KEYWORD); + CHECK(Parse_INVALID_ESCAPE_SEQUENCE); + CHECK(Parse_INVALID_UNICODE_SEQUENCE); + CHECK(Parse_INVALID_NUMBER); + CHECK(Parse_NESTING_DEPTH_REACHED); + CHECK(Parse_UNBALANCED_COLLECTION); + CHECK(Parse_EXPECTED_KEY); + CHECK(Parse_EXPECTED_COLON); + else return "UnknownError"; +#undef CHECK +} + +/** + If CSON_LOG_ALLOC is true then the cson_malloc/realloc/free() routines + will log a message to stderr. +*/ +#define CSON_LOG_ALLOC 0 + + +/** + CSON_FOSSIL_MODE is only for use in the Fossil + source tree, so that we can plug in to its allocators. + We can't do this by, e.g., defining macros for the + malloc/free funcs because fossil's lack of header files + means we would have to #include "main.c" here to + get the declarations. + */ +#if defined(CSON_FOSSIL_MODE) +extern void *fossil_malloc(size_t n); +extern void fossil_free(void *p); +extern void *fossil_realloc(void *p, size_t n); +# define CSON_MALLOC_IMPL fossil_malloc +# define CSON_FREE_IMPL fossil_free +# define CSON_REALLOC_IMPL fossil_realloc +#endif + +#if !defined CSON_MALLOC_IMPL +# define CSON_MALLOC_IMPL malloc +#endif +#if !defined CSON_FREE_IMPL +# define CSON_FREE_IMPL free +#endif +#if !defined CSON_REALLOC_IMPL +# define CSON_REALLOC_IMPL realloc +#endif + +/** + A test/debug macro for simulating an OOM after the given number of + bytes have been allocated. +*/ +#define CSON_SIMULATE_OOM 0 +#if CSON_SIMULATE_OOM +static unsigned int cson_totalAlloced = 0; +#endif + +/** Simple proxy for malloc(). descr is a description of the allocation. */ +static void * cson_malloc( size_t n, char const * descr ) +{ +#if CSON_LOG_ALLOC + fprintf(stderr, "Allocating %u bytes [%s].\n", (unsigned int)n, descr); +#endif +#if CSON_SIMULATE_OOM + cson_totalAlloced += n; + if( cson_totalAlloced > CSON_SIMULATE_OOM ) + { + return NULL; + } +#endif + return CSON_MALLOC_IMPL(n); +} + +/** Simple proxy for free(). descr is a description of the memory being freed. */ +static void cson_free( void * p, char const * descr ) +{ +#if CSON_LOG_ALLOC + fprintf(stderr, "Freeing @%p [%s].\n", p, descr); +#endif + if( !cson_value_is_builtin(p) ) + { + CSON_FREE_IMPL( p ); + } +} +/** Simple proxy for realloc(). descr is a description of the (re)allocation. */ +static void * cson_realloc( void * hint, size_t n, char const * descr ) +{ +#if CSON_LOG_ALLOC + fprintf(stderr, "%sllocating %u bytes [%s].\n", + hint ? "Rea" : "A", + (unsigned int)n, descr); +#endif +#if CSON_SIMULATE_OOM + cson_totalAlloced += n; + if( cson_totalAlloced > CSON_SIMULATE_OOM ) + { + return NULL; + } +#endif + if( 0==n ) + { + cson_free(hint, descr); + return NULL; + } + else + { + return CSON_REALLOC_IMPL( hint, n ); + } +} + + +#undef CSON_LOG_ALLOC +#undef CSON_SIMULATE_OOM + + + +/** + CLIENTS CODE SHOULD NEVER USE THIS because it opens up doors to + memory leaks if it is not used in very controlled circumstances. + Users must be very aware of how the underlying memory management + works. + + Frees any resources owned by val, but does not free val itself + (which may be stack-allocated). If !val or val->api or + val->api->cleanup are NULL then this is a no-op. + + If v is a container type (object or array) its children are also + cleaned up, recursively. + + After calling this, val will have the special "undefined" type. +*/ +static void cson_value_clean( cson_value * val ); + +/** + Increments cv's reference count by 1. As a special case, values + for which cson_value_is_builtin() returns true are not + modified. assert()s if (NULL==cv). +*/ +static void cson_refcount_incr( cson_value * cv ) +{ + assert( NULL != cv ); + if( cson_value_is_builtin( cv ) ) + { /* do nothing: we do not want to modify the shared + instances. + */ + return; + } + else + { + ++cv->refcount; + } +} + +#if 0 +int cson_value_refcount_set( cson_value * cv, unsigned short rc ) +{ + if( NULL == cv ) return cson_rc.ArgError; + else + { + cv->refcount = rc; + return 0; + } +} +#endif + +int cson_value_add_reference( cson_value * cv ) +{ + if( NULL == cv ) return cson_rc.ArgError; + else if( (cv->refcount+1) < cv->refcount ) + { + return cson_rc.RangeError; + } + else + { + cson_refcount_incr( cv ); + return 0; + } +} + +/** + If cv is NULL or cson_value_is_builtin(cv) returns true then this + function does nothing and returns 0, otherwise... If + cv->refcount is 0 or 1 then cson_value_clean(cv) is called, cv is + freed, and 0 is returned. If cv->refcount is any other value then + it is decremented and the new value is returned. +*/ +static cson_counter_t cson_refcount_decr( cson_value * cv ) +{ + if( (NULL == cv) || cson_value_is_builtin(cv) ) return 0; + else if( (0 == cv->refcount) || (0 == --cv->refcount) ) + { + cson_value_clean(cv); + cson_free(cv,"cson_value::refcount=0"); + return 0; + } + else return cv->refcount; +} + +unsigned int cson_string_length_bytes( cson_string const * str ) +{ + return str ? str->length : 0; +} + + +/** + Fetches v's string value as a non-const string. + + cson_strings are intended to be immutable, but this form provides + access to the immutable bits, which are v->length bytes long. A + length-0 string is returned as NULL from here, as opposed to + "". (This is a side-effect of the string allocation mechanism.) + Returns NULL if !v or if v is the internal empty-string singleton. +*/ +static char * cson_string_str(cson_string *v) +{ + /* + See http://groups.google.com/group/comp.lang.c.moderated/browse_thread/thread/2e0c0df5e8a0cd6a + */ +#if 1 + if( !v || (&CSON_EMPTY_HOLDER.stringValue == v) ) return NULL; + else return (char *)((unsigned char *)( v+1 )); +#else + static char empty[2] = {0,0}; + return ( NULL == v ) + ? NULL + : (v->length + ? (char *) (((unsigned char *)v) + sizeof(cson_string)) + : empty) + ; +#endif +} + +/** + Fetches v's string value as a const string. +*/ +char const * cson_string_cstr(cson_string const *v) +{ + /* + See http://groups.google.com/group/comp.lang.c.moderated/browse_thread/thread/2e0c0df5e8a0cd6a + */ +#if 1 + if( ! v ) return NULL; + else if( v == &CSON_EMPTY_HOLDER.stringValue ) return ""; + else { + assert((0 < v->length) && "How do we have a non-singleton empty string?"); + return (char const *)((unsigned char const *)(v+1)); + } +#else + return (NULL == v) + ? NULL + : (v->length + ? (char const *) ((unsigned char const *)(v+1)) + : ""); +#endif +} + + +#if 0 +/** + Just like strndup(3), in that neither are C89/C99-standard and both + are documented in detail in strndup(3). +*/ +static char * cson_strdup( char const * src, size_t n ) +{ + char * rc = (char *)cson_malloc(n+1, "cson_strdup"); + if( ! rc ) return NULL; + memset( rc, 0, n+1 ); + rc[n] = 0; + return strncpy( rc, src, n ); +} +#endif + +int cson_string_cmp_cstr_n( cson_string const * str, char const * other, unsigned int otherLen ) +{ + if( ! other && !str ) return 0; + else if( other && !str ) return 1; + else if( str && !other ) return -1; + else if( !otherLen ) return str->length ? 1 : 0; + else if( !str->length ) return otherLen ? -1 : 0; + else + { + unsigned const int max = (otherLen > str->length) ? otherLen : str->length; + int const rc = strncmp( cson_string_cstr(str), other, max ); + return ( (0 == rc) && (otherLen != str->length) ) + ? (str->length < otherLen) ? -1 : 1 + : rc; + } +} + +int cson_string_cmp_cstr( cson_string const * lhs, char const * rhs ) +{ + return cson_string_cmp_cstr_n( lhs, rhs, (rhs&&*rhs) ? strlen(rhs) : 0 ); +} +int cson_string_cmp( cson_string const * lhs, cson_string const * rhs ) +{ + return cson_string_cmp_cstr_n( lhs, cson_string_cstr(rhs), rhs ? rhs->length : 0 ); +} + + +/** + If self is not NULL, *self is overwritten to have the undefined + type. self is not cleaned up or freed. +*/ +void cson_value_destroy_zero_it( cson_value * self ) +{ + if( self ) + { + *self = cson_value_undef; + } +} + +/** + A key/value pair collection. + + Each of these objects owns its key/value pointers, and they + are cleaned up by cson_kvp_clean(). +*/ +struct cson_kvp +{ + cson_value * key; + cson_value * value; +}; +#define cson_kvp_empty_m {NULL,NULL} +static const cson_kvp cson_kvp_empty = cson_kvp_empty_m; + +/** @def CSON_OBJECT_PROPS_SORT + + Don't use this - it has not been updated to account for internal + changes in cson_object. + + If CSON_OBJECT_PROPS_SORT is set to a true value then + qsort() and bsearch() are used to sort (upon insertion) + and search cson_object::kvp property lists. This costs us + a re-sort on each insertion but searching is O(log n) + average/worst case (and O(1) best-case). + + i'm not yet convinced that the overhead of the qsort() justifies + the potentially decreased search times - it has not been + measured. Object property lists tend to be relatively short in + JSON, and a linear search which uses the cson_string::length + property as a quick check is quite fast when one compares it with + the sort overhead required by the bsearch() approach. +*/ +#define CSON_OBJECT_PROPS_SORT 0 + +/** @def CSON_OBJECT_PROPS_SORT_USE_LENGTH + + Don't use this - i'm not sure that it works how i'd like. + + If CSON_OBJECT_PROPS_SORT_USE_LENGTH is true then + we use string lengths as quick checks when sorting + property keys. This leads to a non-intuitive sorting + order but "should" be faster. + + This is ignored if CSON_OBJECT_PROPS_SORT is false. + +*/ +#define CSON_OBJECT_PROPS_SORT_USE_LENGTH 0 + +#if CSON_OBJECT_PROPS_SORT + +/** + cson_kvp comparator for use with qsort(). ALMOST compares with + strcmp() semantics, but it uses the strings' lengths as a quicker + approach. This might give non-intuitive results, but it's faster. + */ +static int cson_kvp_cmp( void const * lhs, void const * rhs ) +{ + cson_kvp const * lk = *((cson_kvp const * const*)lhs); + cson_kvp const * rk = *((cson_kvp const * const*)rhs); + cson_string const * l = cson_string_value(lk->key); + cson_string const * r = cson_string_value(rk->key); +#if CSON_OBJECT_PROPS_SORT_USE_LENGTH + if( l->length < r->length ) return -1; + else if( l->length > r->length ) return 1; + else return strcmp( cson_string_cstr( l ), cson_string_cstr( r ) ); +#else + return strcmp( cson_string_cstr( l ), + cson_string_cstr( r ) ); +#endif /*CSON_OBJECT_PROPS_SORT_USE_LENGTH*/ +} +#endif /*CSON_OBJECT_PROPS_SORT*/ + + +#if CSON_OBJECT_PROPS_SORT +#error "Need to rework this for cson_string-to-cson_value refactoring" +/** + A bsearch() comparison function which requires that lhs be a (char + const *) and rhs be-a (cson_kvp const * const *). It compares lhs + to rhs->key's value, using strcmp() semantics. + */ +static int cson_kvp_cmp_vs_cstr( void const * lhs, void const * rhs ) +{ + char const * lk = (char const *)lhs; + cson_kvp const * rk = + *((cson_kvp const * const*)rhs) + ; +#if CSON_OBJECT_PROPS_SORT_USE_LENGTH + unsigned int llen = strlen(lk); + if( llen < rk->key->length ) return -1; + else if( llen > rk->key->length ) return 1; + else return strcmp( lk, cson_string_cstr( rk->key ) ); +#else + return strcmp( lk, cson_string_cstr( rk->key ) ); +#endif /*CSON_OBJECT_PROPS_SORT_USE_LENGTH*/ +} +#endif /*CSON_OBJECT_PROPS_SORT*/ + + +struct cson_kvp_list +{ + cson_kvp ** list; + unsigned int count; + unsigned int alloced; +}; +typedef struct cson_kvp_list cson_kvp_list; +#define cson_kvp_list_empty_m {NULL/*list*/,0/*count*/,0/*alloced*/} +/*static const cson_kvp_list cson_kvp_list_empty = cson_kvp_list_empty_m;*/ + +struct cson_object +{ + cson_kvp_list kvp; +}; +/*typedef struct cson_object cson_object;*/ +#define cson_object_empty_m { cson_kvp_list_empty_m/*kvp*/ } +static const cson_object cson_object_empty = cson_object_empty_m; + +struct cson_value_list +{ + cson_value ** list; + unsigned int count; + unsigned int alloced; +}; +typedef struct cson_value_list cson_value_list; +#define cson_value_list_empty_m {NULL/*list*/,0/*count*/,0/*alloced*/} +static const cson_value_list cson_value_list_empty = cson_value_list_empty_m; + +struct cson_array +{ + cson_value_list list; +}; +/*typedef struct cson_array cson_array;*/ +#define cson_array_empty_m { cson_value_list_empty_m/*list*/ } +static const cson_array cson_array_empty = cson_array_empty_m; + + +struct cson_parser +{ + JSON_parser p; + cson_value * root; + cson_value * node; + cson_array stack; + cson_string * ckey; + int errNo; + unsigned int totalKeyCount; + unsigned int totalValueCount; +}; +typedef struct cson_parser cson_parser; +static const cson_parser cson_parser_empty = { +NULL/*p*/, +NULL/*root*/, +NULL/*node*/, +cson_array_empty_m/*stack*/, +NULL/*ckey*/, +0/*errNo*/, +0/*totalKeyCount*/, +0/*totalValueCount*/ +}; + +#if 1 +/* The following funcs are declared in generated code (cson_lists.h), + but we need early access to their decls for the Amalgamation build. +*/ +static unsigned int cson_value_list_reserve( cson_value_list * self, unsigned int n ); +static unsigned int cson_kvp_list_reserve( cson_kvp_list * self, unsigned int n ); +static int cson_kvp_list_append( cson_kvp_list * self, cson_kvp * cp ); +static void cson_kvp_list_clean( cson_kvp_list * self, + void (*cleaner)(cson_kvp * obj) ); +#if 0 +static int cson_value_list_append( cson_value_list * self, cson_value * cp ); +static void cson_value_list_clean( cson_value_list * self, void (*cleaner)(cson_value * obj)); +static int cson_kvp_list_visit( cson_kvp_list * self, + int (*visitor)(cson_kvp * obj, void * visitorState ), + void * visitorState ); +static int cson_value_list_visit( cson_value_list * self, + int (*visitor)(cson_value * obj, void * visitorState ), + void * visitorState ); +#endif +#endif + +#if 0 +# define LIST_T cson_value_list +# define VALUE_T cson_value * +# define VALUE_T_IS_PTR 1 +# define LIST_T cson_kvp_list +# define VALUE_T cson_kvp * +# define VALUE_T_IS_PTR 1 +#else +#endif + +/** + Allocates a new value of the specified type. Ownership is + transfered to the caller, who must eventually free it by passing it + to cson_value_free() or transfering ownership to a container. + + extra is only valid for type CSON_TYPE_STRING, and must be the length + of the string to allocate + 1 byte (for the NUL). + + The returned value->api member will be set appropriately and + val->value will be set to point to the memory allocated to hold the + native value type. Use the internal CSON_CAST() family of macros to + convert the cson_values to their corresponding native + representation. + + Returns NULL on allocation error. + + @see cson_value_new_array() + @see cson_value_new_object() + @see cson_value_new_string() + @see cson_value_new_integer() + @see cson_value_new_double() + @see cson_value_new_bool() + @see cson_value_free() +*/ +static cson_value * cson_value_new(cson_type_id t, size_t extra) +{ + static const size_t vsz = sizeof(cson_value); + const size_t sz = vsz + extra; + size_t tx = 0; + cson_value def = cson_value_undef; + cson_value * v = NULL; + char const * reason = "cson_value_new"; + switch(t) + { + case CSON_TYPE_ARRAY: + assert( 0 == extra ); + def = cson_value_array_empty; + tx = sizeof(cson_array); + reason = "cson_value:array"; + break; + case CSON_TYPE_DOUBLE: + assert( 0 == extra ); + def = cson_value_double_empty; + tx = sizeof(cson_double_t); + reason = "cson_value:double"; + break; + case CSON_TYPE_INTEGER: + assert( 0 == extra ); + def = cson_value_integer_empty; +#if !CSON_VOID_PTR_IS_BIG + tx = sizeof(cson_int_t); +#endif + reason = "cson_value:int"; + break; + case CSON_TYPE_STRING: + assert( 0 != extra ); + def = cson_value_string_empty; + tx = sizeof(cson_string); + reason = "cson_value:string"; + break; + case CSON_TYPE_OBJECT: + assert( 0 == extra ); + def = cson_value_object_empty; + tx = sizeof(cson_object); + reason = "cson_value:object"; + break; + default: + assert(0 && "Unhandled type in cson_value_new()!"); + return NULL; + } + assert( def.api->typeID != CSON_TYPE_UNDEF ); + v = (cson_value *)cson_malloc(sz+tx, reason); + if( v ) { + *v = def; + if(tx || extra){ + memset(v+1, 0, tx + extra); + v->value = (void *)(v+1); + } + } + return v; +} + +void cson_value_free(cson_value *v) +{ + cson_refcount_decr( v ); +} + +#if 0 /* we might actually want this later on. */ +/** Returns true if v is not NULL and has the given type ID. */ +static char cson_value_is_a( cson_value const * v, cson_type_id is ) +{ + return (v && v->api && (v->api->typeID == is)) ? 1 : 0; +} +#endif + +cson_type_id cson_value_type_id( cson_value const * v ) +{ + return (v && v->api) ? v->api->typeID : CSON_TYPE_UNDEF; +} + +char cson_value_is_undef( cson_value const * v ) +{ + return ( !v || !v->api || (v->api==&cson_value_api_undef)) + ? 1 : 0; +} +#define ISA(T,TID) char cson_value_is_##T( cson_value const * v ) { \ + /*return (v && v->api) ? cson_value_is_a(v,CSON_TYPE_##TID) : 0;*/ \ + return (v && (v->api == &cson_value_api_##T)) ? 1 : 0; \ + } extern char bogusPlaceHolderForEmacsIndention##TID +ISA(null,NULL); +ISA(bool,BOOL); +ISA(integer,INTEGER); +ISA(double,DOUBLE); +ISA(string,STRING); +ISA(array,ARRAY); +ISA(object,OBJECT); +#undef ISA +char cson_value_is_number( cson_value const * v ) +{ + return cson_value_is_integer(v) || cson_value_is_double(v); +} + + +void cson_value_clean( cson_value * val ) +{ + if( val && val->api && val->api->cleanup ) + { + if( ! cson_value_is_builtin( val ) ) + { + cson_counter_t const rc = val->refcount; + val->api->cleanup(val); + *val = cson_value_undef; + val->refcount = rc; + } + } +} + +static cson_value * cson_value_array_alloc() +{ + cson_value * v = cson_value_new(CSON_TYPE_ARRAY,0); + if( NULL != v ) + { + cson_array * ar = CSON_ARRAY(v); + assert(NULL != ar); + *ar = cson_array_empty; + } + return v; +} + +static cson_value * cson_value_object_alloc() +{ + cson_value * v = cson_value_new(CSON_TYPE_OBJECT,0); + if( NULL != v ) + { + cson_object * obj = CSON_OBJ(v); + assert(NULL != obj); + *obj = cson_object_empty; + } + return v; +} + +cson_value * cson_value_new_object() +{ + return cson_value_object_alloc(); +} + +cson_object * cson_new_object() +{ + + return cson_value_get_object( cson_value_new_object() ); +} + +cson_value * cson_value_new_array() +{ + return cson_value_array_alloc(); +} + + +cson_array * cson_new_array() +{ + return cson_value_get_array( cson_value_new_array() ); +} + +/** + Frees kvp->key and kvp->value and sets them to NULL, but does not free + kvp. If !kvp then this is a no-op. +*/ +static void cson_kvp_clean( cson_kvp * kvp ) +{ + if( kvp ) + { + if(kvp->key) + { + cson_value_free(kvp->key); + kvp->key = NULL; + } + if(kvp->value) + { + cson_value_free( kvp->value ); + kvp->value = NULL; + } + } +} + +cson_string * cson_kvp_key( cson_kvp const * kvp ) +{ + return kvp ? cson_value_get_string(kvp->key) : NULL; +} +cson_value * cson_kvp_value( cson_kvp const * kvp ) +{ + return kvp ? kvp->value : NULL; +} + + +/** + Calls cson_kvp_clean(kvp) and then frees kvp. +*/ +static void cson_kvp_free( cson_kvp * kvp ) +{ + if( kvp ) + { + cson_kvp_clean(kvp); + cson_free(kvp,"cson_kvp"); + } +} + + +/** + cson_value_api::destroy_value() impl for Object + values. Cleans up self-owned memory and overwrites + self to have the undefined value, but does not + free self. +*/ +static void cson_value_destroy_object( cson_value * self ) +{ + if(self && self->value) { + cson_object * obj = (cson_object *)self->value; + assert( self->value == obj ); + cson_kvp_list_clean( &obj->kvp, cson_kvp_free ); + *self = cson_value_undef; + } +} + +/** + Cleans up the contents of ar->list, but does not free ar. + + After calling this, ar will have a length of 0. + + If properlyCleanValues is 1 then cson_value_free() is called on + each non-NULL item, otherwise the outer list is destroyed but the + individual items are assumed to be owned by someone else and are + not freed. +*/ +static void cson_array_clean( cson_array * ar, char properlyCleanValues ) +{ + if( ar ) + { + unsigned int i = 0; + cson_value * val = NULL; + for( ; i < ar->list.count; ++i ) + { + val = ar->list.list[i]; + if(val) + { + ar->list.list[i] = NULL; + if( properlyCleanValues ) + { + cson_value_free( val ); + } + } + } + cson_value_list_reserve(&ar->list,0); + ar->list = cson_value_list_empty + /* Pedantic note: reserve(0) already clears the list-specific + fields, but we do this just in case we ever add new fields + to cson_value_list which are not used in the reserve() impl. + */ + ; + } +} + +/** + cson_value_api::destroy_value() impl for Array + values. Cleans up self-owned memory and overwrites + self to have the undefined value, but does not + free self. +*/ +static void cson_value_destroy_array( cson_value * self ) +{ + cson_array * ar = cson_value_get_array(self); + if(ar) { + assert( self->value == ar ); + cson_array_clean( ar, 1 ); + *self = cson_value_undef; + } +} + +int cson_buffer_fill_from( cson_buffer * dest, cson_data_source_f src, void * state ) +{ + int rc; + enum { BufSize = 1024 * 4 }; + char rbuf[BufSize]; + size_t total = 0; + unsigned int rlen = 0; + if( ! dest || ! src ) return cson_rc.ArgError; + dest->used = 0; + while(1) + { + rlen = BufSize; + rc = src( state, rbuf, &rlen ); + if( rc ) break; + total += rlen; + if( dest->capacity < (total+1) ) + { + rc = cson_buffer_reserve( dest, total + 1); + if( 0 != rc ) break; + } + memcpy( dest->mem + dest->used, rbuf, rlen ); + dest->used += rlen; + if( rlen < BufSize ) break; + } + if( !rc && dest->used ) + { + assert( dest->used < dest->capacity ); + dest->mem[dest->used] = 0; + } + return rc; +} + +int cson_data_source_FILE( void * state, void * dest, unsigned int * n ) +{ + FILE * f = (FILE*) state; + if( ! state || ! n || !dest ) return cson_rc.ArgError; + else if( !*n ) return cson_rc.RangeError; + *n = (unsigned int)fread( dest, 1, *n, f ); + if( !*n ) + { + return feof(f) ? 0 : cson_rc.IOError; + } + return 0; +} + +int cson_parse_FILE( cson_value ** tgt, FILE * src, + cson_parse_opt const * opt, cson_parse_info * err ) +{ + return cson_parse( tgt, cson_data_source_FILE, src, opt, err ); +} + + +int cson_value_fetch_bool( cson_value const * val, char * v ) +{ + /** + FIXME: move the to-bool operation into cson_value_api, like we + do in the C++ API. + */ + if( ! val || !val->api ) return cson_rc.ArgError; + else + { + int rc = 0; + char b = 0; + switch( val->api->typeID ) + { + case CSON_TYPE_ARRAY: + case CSON_TYPE_OBJECT: + b = 1; + break; + case CSON_TYPE_STRING: { + char const * str = cson_string_cstr(cson_value_get_string(val)); + b = (str && *str) ? 1 : 0; + break; + } + case CSON_TYPE_UNDEF: + case CSON_TYPE_NULL: + break; + case CSON_TYPE_BOOL: + b = (NULL==val->value) ? 0 : 1; + break; + case CSON_TYPE_INTEGER: { + cson_int_t i = 0; + cson_value_fetch_integer( val, &i ); + b = i ? 1 : 0; + break; + } + case CSON_TYPE_DOUBLE: { + cson_double_t d = 0.0; + cson_value_fetch_double( val, &d ); + b = (0.0==d) ? 0 : 1; + break; + } + default: + rc = cson_rc.TypeError; + break; + } + if( v ) *v = b; + return rc; + } +} + +char cson_value_get_bool( cson_value const * val ) +{ + char i = 0; + cson_value_fetch_bool( val, &i ); + return i; +} + +int cson_value_fetch_integer( cson_value const * val, cson_int_t * v ) +{ + if( ! val || !val->api ) return cson_rc.ArgError; + else + { + cson_int_t i = 0; + int rc = 0; + switch(val->api->typeID) + { + case CSON_TYPE_UNDEF: + case CSON_TYPE_NULL: + i = 0; + break; + case CSON_TYPE_BOOL: { + char b = 0; + cson_value_fetch_bool( val, &b ); + i = b; + break; + } + case CSON_TYPE_INTEGER: { + cson_int_t const * x = CSON_INT(val); + if(!x) + { + assert( val == &CSON_SPECIAL_VALUES[CSON_VAL_INT_0] ); + } + i = x ? *x : 0; + break; + } + case CSON_TYPE_DOUBLE: { + cson_double_t d = 0.0; + cson_value_fetch_double( val, &d ); + i = (cson_int_t)d; + break; + } + case CSON_TYPE_STRING: + case CSON_TYPE_ARRAY: + case CSON_TYPE_OBJECT: + default: + rc = cson_rc.TypeError; + break; + } + if(!rc && v) *v = i; + return rc; + } +} + +cson_int_t cson_value_get_integer( cson_value const * val ) +{ + cson_int_t i = 0; + cson_value_fetch_integer( val, &i ); + return i; +} + +int cson_value_fetch_double( cson_value const * val, cson_double_t * v ) +{ + if( ! val || !val->api ) return cson_rc.ArgError; + else + { + cson_double_t d = 0.0; + int rc = 0; + switch(val->api->typeID) + { + case CSON_TYPE_UNDEF: + case CSON_TYPE_NULL: + d = 0; + break; + case CSON_TYPE_BOOL: { + char b = 0; + cson_value_fetch_bool( val, &b ); + d = b ? 1.0 : 0.0; + break; + } + case CSON_TYPE_INTEGER: { + cson_int_t i = 0; + cson_value_fetch_integer( val, &i ); + d = i; + break; + } + case CSON_TYPE_DOUBLE: { + cson_double_t const* dv = CSON_DBL(val); + d = dv ? *dv : 0.0; + break; + } + default: + rc = cson_rc.TypeError; + break; + } + if(v) *v = d; + return rc; + } +} + +cson_double_t cson_value_get_double( cson_value const * val ) +{ + cson_double_t i = 0.0; + cson_value_fetch_double( val, &i ); + return i; +} + +int cson_value_fetch_string( cson_value const * val, cson_string ** dest ) +{ + if( ! val || ! dest ) return cson_rc.ArgError; + else if( ! cson_value_is_string(val) ) return cson_rc.TypeError; + else + { + if( dest ) *dest = CSON_STR(val); + return 0; + } +} + +cson_string * cson_value_get_string( cson_value const * val ) +{ + cson_string * rc = NULL; + cson_value_fetch_string( val, &rc ); + return rc; +} + +char const * cson_value_get_cstr( cson_value const * val ) +{ + return cson_string_cstr( cson_value_get_string(val) ); +} + +int cson_value_fetch_object( cson_value const * val, cson_object ** obj ) +{ + if( ! val ) return cson_rc.ArgError; + else if( ! cson_value_is_object(val) ) return cson_rc.TypeError; + else + { + if(obj) *obj = CSON_OBJ(val); + return 0; + } +} +cson_object * cson_value_get_object( cson_value const * v ) +{ + cson_object * obj = NULL; + cson_value_fetch_object( v, &obj ); + return obj; +} + +int cson_value_fetch_array( cson_value const * val, cson_array ** ar) +{ + if( ! val ) return cson_rc.ArgError; + else if( !cson_value_is_array(val) ) return cson_rc.TypeError; + else + { + if(ar) *ar = CSON_ARRAY(val); + return 0; + } +} + +cson_array * cson_value_get_array( cson_value const * v ) +{ + cson_array * ar = NULL; + cson_value_fetch_array( v, &ar ); + return ar; +} + +cson_kvp * cson_kvp_alloc() +{ + cson_kvp * kvp = (cson_kvp*)cson_malloc(sizeof(cson_kvp),"cson_kvp"); + if( kvp ) + { + *kvp = cson_kvp_empty; + } + return kvp; +} + + + +int cson_array_append( cson_array * ar, cson_value * v ) +{ + if( !ar || !v ) return cson_rc.ArgError; + else if( (ar->list.count+1) < ar->list.count ) return cson_rc.RangeError; + else + { + if( !ar->list.alloced || (ar->list.count == ar->list.alloced-1)) + { + unsigned int const n = ar->list.count ? (ar->list.count*2) : 7; + if( n > cson_value_list_reserve( &ar->list, n ) ) + { + return cson_rc.AllocError; + } + } + return cson_array_set( ar, ar->list.count, v ); + } +} + +#if 0 +/** + Removes and returns the last value from the given array, + shrinking its size by 1. Returns NULL if ar is NULL, + ar->list.count is 0, or the element at that index is NULL. + + + If removeRef is true then cson_value_free() is called to remove + ar's reference count for the value. In that case NULL is returned, + even if the object still has live references. If removeRef is false + then the caller takes over ownership of that reference count point. + + If removeRef is false then the caller takes over ownership + of the return value, otherwise ownership is effectively + determined by any remaining references for the returned + value. +*/ +static cson_value * cson_array_pop_back( cson_array * ar, + char removeRef ) +{ + if( !ar ) return NULL; + else if( ! ar->list.count ) return NULL; + else + { + unsigned int const ndx = --ar->list.count; + cson_value * v = ar->list.list[ndx]; + ar->list.list[ndx] = NULL; + if( removeRef ) + { + cson_value_free( v ); + v = NULL; + } + return v; + } +} +#endif + +cson_value * cson_value_new_bool( char v ) +{ + return v ? &CSON_SPECIAL_VALUES[CSON_VAL_TRUE] : &CSON_SPECIAL_VALUES[CSON_VAL_FALSE]; +} + +cson_value * cson_value_true() +{ + return &CSON_SPECIAL_VALUES[CSON_VAL_TRUE]; +} +cson_value * cson_value_false() +{ + return &CSON_SPECIAL_VALUES[CSON_VAL_FALSE]; +} + +cson_value * cson_value_null() +{ + return &CSON_SPECIAL_VALUES[CSON_VAL_NULL]; +} + +cson_value * cson_new_int( cson_int_t v ) +{ + return cson_value_new_integer(v); +} + +cson_value * cson_value_new_integer( cson_int_t v ) +{ + if( 0 == v ) return &CSON_SPECIAL_VALUES[CSON_VAL_INT_0]; + else + { + cson_value * c = cson_value_new(CSON_TYPE_INTEGER,0); +#if !defined(NDEBUG) && CSON_VOID_PTR_IS_BIG + assert( sizeof(cson_int_t) <= sizeof(void *) ); +#endif + if( c ) + { + memcpy( CSON_INT(c), &v, sizeof(v) ); + } + return c; + } +} + +cson_value * cson_new_double( cson_double_t v ) +{ + return cson_value_new_double(v); +} + +cson_value * cson_value_new_double( cson_double_t v ) +{ + if( 0.0 == v ) return &CSON_SPECIAL_VALUES[CSON_VAL_DBL_0]; + else + { + cson_value * c = cson_value_new(CSON_TYPE_DOUBLE,0); + if( c ) + { + memcpy( CSON_DBL(c), &v, sizeof(v) ); + } + return c; + } +} + +cson_string * cson_new_string(char const * str, unsigned int len) +{ + if( !str || !*str || !len ) return &CSON_EMPTY_HOLDER.stringValue; + else + { + cson_value * c = cson_value_new(CSON_TYPE_STRING, len + 1/*NUL byte*/); + cson_string * s = NULL; + if( c ) + { + char * dest = NULL; + s = CSON_STR(c); + *s = cson_string_empty; + assert( NULL != s ); + s->length = len; + dest = cson_string_str(s); + assert( NULL != dest ); + memcpy( dest, str, len ); + dest[len] = 0; + } + return s; + } +} + +cson_value * cson_value_new_string( char const * str, unsigned int len ) +{ + return cson_string_value( cson_new_string(str, len) ); +} + +int cson_array_value_fetch( cson_array const * ar, unsigned int pos, cson_value ** v ) +{ + if( !ar) return cson_rc.ArgError; + if( pos >= ar->list.count ) return cson_rc.RangeError; + else + { + if(v) *v = ar->list.list[pos]; + return 0; + } +} + +cson_value * cson_array_get( cson_array const * ar, unsigned int pos ) +{ + cson_value *v = NULL; + cson_array_value_fetch(ar, pos, &v); + return v; +} + +int cson_array_length_fetch( cson_array const * ar, unsigned int * v ) +{ + if( ! ar || !v ) return cson_rc.ArgError; + else + { + if(v) *v = ar->list.count; + return 0; + } +} + +unsigned int cson_array_length_get( cson_array const * ar ) +{ + unsigned int i = 0; + cson_array_length_fetch(ar, &i); + return i; +} + +int cson_array_reserve( cson_array * ar, unsigned int size ) +{ + if( ! ar ) return cson_rc.ArgError; + else if( size <= ar->list.alloced ) + { + /* We don't want to introduce a can of worms by trying to + handle the cleanup from here. + */ + return 0; + } + else + { + return (ar->list.alloced > cson_value_list_reserve( &ar->list, size )) + ? cson_rc.AllocError + : 0 + ; + } +} + +int cson_array_set( cson_array * ar, unsigned int ndx, cson_value * v ) +{ + if( !ar || !v ) return cson_rc.ArgError; + else if( (ndx+1) < ndx) /* overflow */return cson_rc.RangeError; + else + { + unsigned const int len = cson_value_list_reserve( &ar->list, ndx+1 ); + if( len <= ndx ) return cson_rc.AllocError; + else + { + cson_value * old = ar->list.list[ndx]; + if( old ) + { + if(old == v) return 0; + else cson_value_free(old); + } + cson_refcount_incr( v ); + ar->list.list[ndx] = v; + if( ndx >= ar->list.count ) + { + ar->list.count = ndx+1; + } + return 0; + } + } +} + +/** @internal + + Searchs for the given key in the given object. + + Returns the found item on success, NULL on error. If ndx is not + NULL, it is set to the index (in obj->kvp.list) of the found + item. *ndx is not modified if no entry is found. +*/ +static cson_kvp * cson_object_search_impl( cson_object const * obj, char const * key, unsigned int * ndx ) +{ + if( obj && key && *key && obj->kvp.count) + { +#if CSON_OBJECT_PROPS_SORT + cson_kvp ** s = (cson_kvp**) + bsearch( key, obj->kvp.list, + obj->kvp.count, sizeof(cson_kvp*), + cson_kvp_cmp_vs_cstr ); + if( ndx && s ) + { /* index of found record is required by + cson_object_unset(). Calculate the offset based on s...*/ +#if 0 + *ndx = (((unsigned char const *)s - ((unsigned char const *)obj->kvp.list)) + / sizeof(cson_kvp*)); +#else + *ndx = s - obj->kvp.list; +#endif + } + return s ? *s : NULL; +#else + cson_kvp_list const * li = &obj->kvp; + unsigned int i = 0; + cson_kvp * kvp; + const unsigned int klen = strlen(key); + for( ; i < li->count; ++i ) + { + cson_string const * sKey; + kvp = li->list[i]; + assert( kvp && kvp->key ); + sKey = cson_value_get_string(kvp->key); + assert(sKey); + if( sKey->length != klen ) continue; + else if(0==strcmp(key,cson_string_cstr(sKey))) + { + if(ndx) *ndx = i; + return kvp; + } + } +#endif + } + return NULL; +} + +cson_value * cson_object_get( cson_object const * obj, char const * key ) +{ + cson_kvp * kvp = cson_object_search_impl( obj, key, NULL ); + return kvp ? kvp->value : NULL; +} + +cson_value * cson_object_get_s( cson_object const * obj, cson_string const *key ) +{ + cson_kvp * kvp = cson_object_search_impl( obj, cson_string_cstr(key), NULL ); + return kvp ? kvp->value : NULL; +} + + +#if CSON_OBJECT_PROPS_SORT +static void cson_object_sort_props( cson_object * obj ) +{ + assert( NULL != obj ); + if( obj->kvp.count ) + { + qsort( obj->kvp.list, obj->kvp.count, sizeof(cson_kvp*), + cson_kvp_cmp ); + } + +} +#endif + +int cson_object_unset( cson_object * obj, char const * key ) +{ + if( ! obj || !key || !*key ) return cson_rc.ArgError; + else + { + unsigned int ndx = 0; + cson_kvp * kvp = cson_object_search_impl( obj, key, &ndx ); + if( ! kvp ) + { + return cson_rc.NotFoundError; + } + assert( obj->kvp.count > 0 ); + assert( obj->kvp.list[ndx] == kvp ); + cson_kvp_free( kvp ); + obj->kvp.list[ndx] = NULL; + { /* if my brain were bigger i'd use memmove(). */ + unsigned int i = ndx; + for( ; i < obj->kvp.count; ++i ) + { + obj->kvp.list[i] = + (i < (obj->kvp.alloced-1)) + ? obj->kvp.list[i+1] + : NULL; + } + } + obj->kvp.list[--obj->kvp.count] = NULL; +#if CSON_OBJECT_PROPS_SORT + cson_object_sort_props( obj ); +#endif + return 0; + } +} + +int cson_object_set_s( cson_object * obj, cson_string * key, cson_value * v ) +{ + if( !obj || !key ) return cson_rc.ArgError; + else if( NULL == v ) return cson_object_unset( obj, cson_string_cstr(key) ); + else + { + char const * cKey; + cson_value * vKey; + cson_kvp * kvp; + vKey = cson_string_value(key); + assert(vKey && (key==CSON_STR(vKey))); + if( vKey == CSON_VCAST(obj) ){ + return cson_rc.ArgError; + } + cKey = cson_string_cstr(key); + kvp = cson_object_search_impl( obj, cKey, NULL ); + if( kvp ) + { /* "I told 'em we've already got one!" */ + if( kvp->key != vKey ){ + cson_value_free( kvp->key ); + cson_refcount_incr(vKey); + kvp->key = vKey; + } + if(kvp->value != v){ + cson_value_free( kvp->value ); + cson_refcount_incr( v ); + kvp->value = v; + } + return 0; + } + if( !obj->kvp.alloced || (obj->kvp.count == obj->kvp.alloced-1)) + { /* reserve space */ + unsigned int const n = obj->kvp.count ? (obj->kvp.count*2) : 6; + if( n > cson_kvp_list_reserve( &obj->kvp, n ) ) + { + return cson_rc.AllocError; + } + } + { /* insert new item... */ + int rc = 0; + kvp = cson_kvp_alloc(); + if( ! kvp ) + { + return cson_rc.AllocError; + } + rc = cson_kvp_list_append( &obj->kvp, kvp ); + if( 0 != rc ) + { + cson_kvp_free(kvp); + } + else + { + cson_refcount_incr(vKey); + cson_refcount_incr(v); + kvp->key = vKey; + kvp->value = v; +#if CSON_OBJECT_PROPS_SORT + cson_object_sort_props( obj ); +#endif + } + return rc; + } + } + +} +int cson_object_set( cson_object * obj, char const * key, cson_value * v ) +{ + if( ! obj || !key || !*key ) return cson_rc.ArgError; + else if( NULL == v ) + { + return cson_object_unset( obj, key ); + } + else + { + cson_string * cs = cson_new_string(key,strlen(key)); + if(!cs) return cson_rc.AllocError; + else + { + int const rc = cson_object_set_s(obj, cs, v); + if(rc) cson_value_free(cson_string_value(cs)); + return rc; + } + } +} + +cson_value * cson_object_take( cson_object * obj, char const * key ) +{ + if( ! obj || !key || !*key ) return NULL; + else + { + /* FIXME: this is 90% identical to cson_object_unset(), + only with different refcount handling. + Consolidate them. + */ + unsigned int ndx = 0; + cson_kvp * kvp = cson_object_search_impl( obj, key, &ndx ); + cson_value * rc = NULL; + if( ! kvp ) + { + return NULL; + } + assert( obj->kvp.count > 0 ); + assert( obj->kvp.list[ndx] == kvp ); + rc = kvp->value; + assert( rc ); + kvp->value = NULL; + cson_kvp_free( kvp ); + assert( rc->refcount > 0 ); + --rc->refcount; + obj->kvp.list[ndx] = NULL; + { /* if my brain were bigger i'd use memmove(). */ + unsigned int i = ndx; + for( ; i < obj->kvp.count; ++i ) + { + obj->kvp.list[i] = + (i < (obj->kvp.alloced-1)) + ? obj->kvp.list[i+1] + : NULL; + } + } + obj->kvp.list[--obj->kvp.count] = NULL; +#if CSON_OBJECT_PROPS_SORT + cson_object_sort_props( obj ); +#endif + return rc; + } +} +/** @internal + + If p->node is-a Object then value is inserted into the object + using p->key. In any other case cson_rc.InternalError is returned. + + Returns cson_rc.AllocError if an allocation fails. + + Returns 0 on success. On error, parsing must be ceased immediately. + + Ownership of val is ALWAYS TRANSFERED to this function. If this + function fails, val will be cleaned up and destroyed. (This + simplifies error handling in the core parser.) +*/ +static int cson_parser_set_key( cson_parser * p, cson_value * val ) +{ + assert( p && val ); + + if( p->ckey && cson_value_is_object(p->node) ) + { + int rc; + cson_object * obj = cson_value_get_object(p->node); + cson_kvp * kvp = NULL; + assert( obj && (p->node->value == obj) ); + /** + FIXME? Use cson_object_set() instead of our custom + finagling with the object? We do it this way to avoid an + extra alloc/strcpy of the key data. + */ + if( !obj->kvp.alloced || (obj->kvp.count == obj->kvp.alloced-1)) + { + if( obj->kvp.alloced > cson_kvp_list_reserve( &obj->kvp, obj->kvp.count ? (obj->kvp.count*2) : 5 ) ) + { + cson_value_free(val); + return cson_rc.AllocError; + } + } + kvp = cson_kvp_alloc(); + if( ! kvp ) + { + cson_value_free(val); + return cson_rc.AllocError; + } + kvp->key = cson_string_value(p->ckey)/*transfer ownership*/; + assert(0 == kvp->key->refcount); + cson_refcount_incr(kvp->key); + p->ckey = NULL; + kvp->value = val; + cson_refcount_incr( val ); + rc = cson_kvp_list_append( &obj->kvp, kvp ); + if( 0 != rc ) + { + cson_kvp_free( kvp ); + } + else + { + ++p->totalValueCount; + } + return rc; + } + else + { + if(val) cson_value_free(val); + return p->errNo = cson_rc.InternalError; + } + +} + +/** @internal + + Pushes val into the current object/array parent node, depending on the + internal state of the parser. + + Ownership of val is always transfered to this function, regardless of + success or failure. + + Returns 0 on success. On error, parsing must be ceased immediately. +*/ +static int cson_parser_push_value( cson_parser * p, cson_value * val ) +{ + if( p->ckey ) + { /* we're in Object mode */ + assert( cson_value_is_object( p->node ) ); + return cson_parser_set_key( p, val ); + } + else if( cson_value_is_array( p->node ) ) + { /* we're in Array mode */ + cson_array * ar = cson_value_get_array( p->node ); + int rc; + assert( ar && (ar == p->node->value) ); + rc = cson_array_append( ar, val ); + if( 0 != rc ) + { + cson_value_free(val); + } + else + { + ++p->totalValueCount; + } + return rc; + } + else + { /* WTF? */ + assert( 0 && "Internal error in cson_parser code" ); + return p->errNo = cson_rc.InternalError; + } +} + +/** + Callback for JSON_parser API. Reminder: it returns 0 (meaning false) + on error! +*/ +static int cson_parse_callback( void * cx, int type, JSON_value const * value ) +{ + cson_parser * p = (cson_parser *)cx; + int rc = 0; +#define ALLOC_V(T,V) cson_value * v = cson_value_new_##T(V); if( ! v ) { rc = cson_rc.AllocError; break; } + switch(type) { + case JSON_T_ARRAY_BEGIN: + case JSON_T_OBJECT_BEGIN: { + cson_value * obja = (JSON_T_ARRAY_BEGIN == type) + ? cson_value_new_array() + : cson_value_new_object(); + if( ! obja ) + { + p->errNo = cson_rc.AllocError; + break; + } + if( 0 != rc ) break; + if( ! p->root ) + { + p->root = p->node = obja; + rc = cson_array_append( &p->stack, obja ); + if( 0 != rc ) + { /* work around a (potential) corner case in the cleanup code. */ + cson_value_free( p->root ); + p->root = NULL; + } + else + { + cson_refcount_incr( p->root ) + /* simplifies cleanup later on. */ + ; + ++p->totalValueCount; + } + } + else + { + rc = cson_array_append( &p->stack, obja ); + if(rc) cson_value_free( obja ); + else + { + rc = cson_parser_push_value( p, obja ); + if( 0 == rc ) p->node = obja; + } + } + break; + } + case JSON_T_ARRAY_END: + case JSON_T_OBJECT_END: { + if( 0 == p->stack.list.count ) + { + rc = cson_rc.RangeError; + break; + } +#if CSON_OBJECT_PROPS_SORT + if( cson_value_is_object(p->node) ) + {/* kludge: the parser uses custom cson_object property + insertion as a malloc/strcpy-reduction optimization. + Because of that, we have to sort the property list + ourselves... + */ + cson_object * obj = cson_value_get_object(p->node); + assert( NULL != obj ); + cson_object_sort_props( obj ); + } +#endif + +#if 1 + /* Reminder: do not use cson_array_pop_back( &p->stack ) + because that will clean up the object, and we don't want + that. We just want to forget this reference + to it. The object is either the root or was pushed into + an object/array in the parse tree (and is owned by that + object/array). + */ + --p->stack.list.count; + assert( p->node == p->stack.list.list[p->stack.list.count] ); + cson_refcount_decr( p->node ) + /* p->node might be owned by an outer object but we + need to remove the list's reference. For the + root node we manually add a reference to + avoid a special case here. Thus when we close + the root node, its refcount is still 1. + */; + p->stack.list.list[p->stack.list.count] = NULL; + if( p->stack.list.count ) + { + p->node = p->stack.list.list[p->stack.list.count-1]; + } + else + { + p->node = p->root; + } +#else + /* + Causing a leak? + */ + cson_array_pop_back( &p->stack, 1 ); + if( p->stack.list.count ) + { + p->node = p->stack.list.list[p->stack.list.count-1]; + } + else + { + p->node = p->root; + } + assert( p->node && (1==p->node->refcount) ); +#endif + break; + } + case JSON_T_INTEGER: { + ALLOC_V(integer, value->vu.integer_value ); + rc = cson_parser_push_value( p, v ); + break; + } + case JSON_T_FLOAT: { + ALLOC_V(double, value->vu.float_value ); + rc = cson_parser_push_value( p, v ); + break; + } + case JSON_T_NULL: { + rc = cson_parser_push_value( p, cson_value_null() ); + break; + } + case JSON_T_TRUE: { + rc = cson_parser_push_value( p, cson_value_true() ); + break; + } + case JSON_T_FALSE: { + rc = cson_parser_push_value( p, cson_value_false() ); + break; + } + case JSON_T_KEY: { + assert(!p->ckey); + p->ckey = cson_new_string( value->vu.str.value, value->vu.str.length ); + if( ! p->ckey ) + { + rc = cson_rc.AllocError; + break; + } + ++p->totalKeyCount; + break; + } + case JSON_T_STRING: { + cson_value * v = cson_value_new_string( value->vu.str.value, value->vu.str.length ); + rc = ( NULL == v ) + ? cson_rc.AllocError + : cson_parser_push_value( p, v ); + break; + } + default: + assert(0); + rc = cson_rc.InternalError; + break; + } +#undef ALLOC_V + return ((p->errNo = rc)) ? 0 : 1; +} + + +/** + Converts a JSON_error code to one of the cson_rc values. +*/ +static int cson_json_err_to_rc( JSON_error jrc ) +{ + switch(jrc) + { + case JSON_E_NONE: return 0; + case JSON_E_INVALID_CHAR: return cson_rc.Parse_INVALID_CHAR; + case JSON_E_INVALID_KEYWORD: return cson_rc.Parse_INVALID_KEYWORD; + case JSON_E_INVALID_ESCAPE_SEQUENCE: return cson_rc.Parse_INVALID_ESCAPE_SEQUENCE; + case JSON_E_INVALID_UNICODE_SEQUENCE: return cson_rc.Parse_INVALID_UNICODE_SEQUENCE; + case JSON_E_INVALID_NUMBER: return cson_rc.Parse_INVALID_NUMBER; + case JSON_E_NESTING_DEPTH_REACHED: return cson_rc.Parse_NESTING_DEPTH_REACHED; + case JSON_E_UNBALANCED_COLLECTION: return cson_rc.Parse_UNBALANCED_COLLECTION; + case JSON_E_EXPECTED_KEY: return cson_rc.Parse_EXPECTED_KEY; + case JSON_E_EXPECTED_COLON: return cson_rc.Parse_EXPECTED_COLON; + case JSON_E_OUT_OF_MEMORY: return cson_rc.AllocError; + default: + return cson_rc.InternalError; + } +} + +/** @internal + + Cleans up all contents of p but does not free p. + + To properly take over ownership of the parser's root node on a + successful parse: + + - Copy p->root's pointer and set p->root to NULL. + - Eventually free up p->root with cson_value_free(). + + If you do not set p->root to NULL, p->root will be freed along with + any other items inserted into it (or under it) during the parsing + process. +*/ +static int cson_parser_clean( cson_parser * p ) +{ + if( ! p ) return cson_rc.ArgError; + else + { + if( p->p ) + { + delete_JSON_parser(p->p); + p->p = NULL; + } + if( p->ckey ){ + cson_value_free(cson_string_value(p->ckey)); + } + cson_array_clean( &p->stack, 1 ); + if( p->root ) + { + cson_value_free( p->root ); + } + *p = cson_parser_empty; + return 0; + } +} + + +int cson_parse( cson_value ** tgt, cson_data_source_f src, void * state, + cson_parse_opt const * opt_, cson_parse_info * info_ ) +{ + unsigned char ch[2] = {0,0}; + cson_parse_opt const opt = opt_ ? *opt_ : cson_parse_opt_empty; + int rc = 0; + unsigned int len = 1; + cson_parse_info info = info_ ? *info_ : cson_parse_info_empty; + cson_parser p = cson_parser_empty; + if( ! tgt || ! src ) return cson_rc.ArgError; + + { + JSON_config jopt = {0}; + init_JSON_config( &jopt ); + jopt.allow_comments = opt.allowComments; + jopt.depth = opt.maxDepth; + jopt.callback_ctx = &p; + jopt.handle_floats_manually = 0; + jopt.callback = cson_parse_callback; + p.p = new_JSON_parser(&jopt); + if( ! p.p ) + { + return cson_rc.AllocError; + } + } + + do + { /* FIXME: buffer the input in multi-kb chunks. */ + len = 1; + ch[0] = 0; + rc = src( state, ch, &len ); + if( 0 != rc ) break; + else if( !len /* EOF */ ) break; + ++info.length; + if('\n' == ch[0]) + { + ++info.line; + info.col = 0; + } + if( ! JSON_parser_char(p.p, ch[0]) ) + { + rc = cson_json_err_to_rc( JSON_parser_get_last_error(p.p) ); + if(0==rc) rc = p.errNo; + if(0==rc) rc = cson_rc.InternalError; + info.errorCode = rc; + break; + } + if( '\n' != ch[0]) ++info.col; + } while(1); + if( info_ ) + { + info.totalKeyCount = p.totalKeyCount; + info.totalValueCount = p.totalValueCount; + *info_ = info; + } + if( 0 != rc ) + { + cson_parser_clean(&p); + return rc; + } + if( ! JSON_parser_done(p.p) ) + { + rc = cson_json_err_to_rc( JSON_parser_get_last_error(p.p) ); + cson_parser_clean(&p); + if(0==rc) rc = p.errNo; + if(0==rc) rc = cson_rc.InternalError; + } + else + { + cson_value * root = p.root; + p.root = NULL; + cson_parser_clean(&p); + if( root ) + { + assert( (1 == root->refcount) && "Detected memory mismanagement in the parser." ); + root->refcount = 0 + /* HUGE KLUDGE! Avoids having one too many references + in some client code, leading to a leak. Here we're + accommodating a memory management workaround in the + parser code which manually adds a reference to the + root node to keep it from being cleaned up + prematurely. + */; + *tgt = root; + } + else + { /* then can happen on empty input. */ + rc = cson_rc.UnknownError; + } + } + return rc; +} + +/** + The UTF code was originally taken from sqlite3's public-domain + source code (http://sqlite.org), modified only slightly for use + here. This code generates some "possible data loss" warnings on + MSVC, but if this code is good enough for sqlite3 then it's damned + well good enough for me, so we disable that warning for Windows + builds. +*/ + +/* +** This lookup table is used to help decode the first byte of +** a multi-byte UTF8 character. +*/ +static const unsigned char cson_utfTrans1[] = { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x00, 0x01, 0x02, 0x03, 0x00, 0x01, 0x00, 0x00 +}; + + +/* +** Translate a single UTF-8 character. Return the unicode value. +** +** During translation, assume that the byte that zTerm points +** is a 0x00. +** +** Write a pointer to the next unread byte back into *pzNext. +** +** Notes On Invalid UTF-8: +** +** * This routine never allows a 7-bit character (0x00 through 0x7f) to +** be encoded as a multi-byte character. Any multi-byte character that +** attempts to encode a value between 0x00 and 0x7f is rendered as 0xfffd. +** +** * This routine never allows a UTF16 surrogate value to be encoded. +** If a multi-byte character attempts to encode a value between +** 0xd800 and 0xe000 then it is rendered as 0xfffd. +** +** * Bytes in the range of 0x80 through 0xbf which occur as the first +** byte of a character are interpreted as single-byte characters +** and rendered as themselves even though they are technically +** invalid characters. +** +** * This routine accepts an infinite number of different UTF8 encodings +** for unicode values 0x80 and greater. It do not change over-length +** encodings to 0xfffd as some systems recommend. +*/ +#define READ_UTF8(zIn, zTerm, c) \ + c = *(zIn++); \ + if( c>=0xc0 ){ \ + c = cson_utfTrans1[c-0xc0]; \ + while( zIn!=zTerm && (*zIn & 0xc0)==0x80 ){ \ + c = (c<<6) + (0x3f & *(zIn++)); \ + } \ + if( c<0x80 \ + || (c&0xFFFFF800)==0xD800 \ + || (c&0xFFFFFFFE)==0xFFFE ){ c = 0xFFFD; } \ + } +static int cson_utf8Read( + const unsigned char *z, /* First byte of UTF-8 character */ + const unsigned char *zTerm, /* Pretend this byte is 0x00 */ + const unsigned char **pzNext /* Write first byte past UTF-8 char here */ +){ + int c; + READ_UTF8(z, zTerm, c); + *pzNext = z; + return c; +} +#undef READ_UTF8 + +#ifdef _MSC_VER +# if _MSC_VER >= 1400 /* Visual Studio 2005 and up */ +# pragma warning( pop ) +# endif +#endif + +unsigned int cson_string_length_utf8( cson_string const * str ) +{ + if( ! str ) return 0; + else + { + char unsigned const * pos = (char unsigned const *)cson_string_cstr(str); + char unsigned const * end = pos + str->length; + unsigned int rc = 0; + for( ; (pos < end) && cson_utf8Read(pos, end, &pos); + ++rc ) + { + }; + return rc; + } +} + +/** + Escapes the first len bytes of the given string as JSON and sends + it to the given output function (which will be called often - once + for each logical character). The output is also surrounded by + double-quotes. + + A NULL str will be escaped as an empty string, though we should + arguably export it as "null" (without quotes). We do this because + in JavaScript (typeof null === "object"), and by outputing null + here we would effectively change the data type from string to + object. +*/ +static int cson_str_to_json( char const * str, unsigned int len, + char escapeFwdSlash, + cson_data_dest_f f, void * state ) +{ + if( NULL == f ) return cson_rc.ArgError; + else if( !str || !*str || (0 == len) ) + { /* special case for 0-length strings. */ + return f( state, "\"\"", 2 ); + } + else + { + unsigned char const * pos = (unsigned char const *)str; + unsigned char const * end = (unsigned char const *)(str ? (str + len) : NULL); + unsigned char const * next = NULL; + int ch; + unsigned char clen = 0; + char escChar[3] = {'\\',0,0}; + enum { UBLen = 20 }; + char ubuf[UBLen]; + int rc = 0; + rc = f(state, "\"", 1 ); + for( ; (pos < end) && (0 == rc); pos += clen ) + { + ch = cson_utf8Read(pos, end, &next); + if( 0 == ch ) break; + assert( next > pos ); + clen = next - pos; + assert( clen ); + if( 1 == clen ) + { /* ASCII */ +#if defined(CSON_FOSSIL_MODE) + /* Workaround for fossil repo artifact + f460839cff85d4e4f1360b366bb2858cef1411ea, + which has what appears to be latin1-encoded + text. file(1) thinks it's a FORTRAN program. + */ + if(0xfffd==ch){ + assert(*pos != ch); + /* MARKER("ch=%04x, *pos=%04x\n", ch, *pos); */ + ch = *pos + /* We should arguably translate to '?', and + will if this problem ever comes up with a + non-latin1 encoding. For latin1 this + workaround incidentally corrects the output + to proper UTF8-escaped characters, and only + for that reason is it being kept around. + */; + goto assume_latin1; + } +#endif + assert( (*pos == ch) && "Invalid UTF8" ); + escChar[1] = 0; + switch(ch) + { + case '\t': escChar[1] = 't'; break; + case '\r': escChar[1] = 'r'; break; + case '\n': escChar[1] = 'n'; break; + case '\f': escChar[1] = 'f'; break; + case '\b': escChar[1] = 'b'; break; + case '/': + /* + Regarding escaping of forward-slashes. See the main exchange below... + + -------------- + From: Douglas Crockford + To: Stephan Beal + Subject: Re: Is escaping of forward slashes required? + + It is allowed, not required. It is allowed so that JSON can be safely + embedded in HTML, which can freak out when seeing strings containing + " Hello, Jsonites, + > + > i'm a bit confused on a small grammatic detail of JSON: + > + > if i'm reading the grammar chart on http://www.json.org/ correctly, + > forward slashes (/) are supposed to be escaped in JSON. However, the + > JSON class provided with my browsers (Chrome and FF, both of which i + > assume are fairly standards/RFC-compliant) do not escape such characters. + > + > Is backslash-escaping forward slashes required? If so, what is the + > justification for it? (i ask because i find it unnecessary and hard to + > look at.) + -------------- + */ + if( escapeFwdSlash ) escChar[1] = '/'; + break; + case '\\': escChar[1] = '\\'; break; + case '"': escChar[1] = '"'; break; + default: break; + } + if( escChar[1]) + { + rc = f(state, escChar, 2); + } + else + { + rc = f(state, (char const *)pos, clen); + } + continue; + } + else + { /* UTF: transform it to \uXXXX */ +#if defined(CSON_FOSSIL_MODE) + assume_latin1: +#endif + memset(ubuf,0,UBLen); + if(ch <= 0xFFFF){ + rc = sprintf(ubuf, "\\u%04x",ch); + if( rc != 6 ) + { + rc = cson_rc.RangeError; + break; + } + rc = f( state, ubuf, 6 ); + }else{ /* encode as a UTF16 surrogate pair */ + /* http://unicodebook.readthedocs.org/en/latest/unicode_encodings.html#surrogates */ + ch -= 0x10000; + rc = sprintf(ubuf, "\\u%04x\\u%04x", + (0xd800 | (ch>>10)), + (0xdc00 | (ch & 0x3ff))); + if( rc != 12 ) + { + rc = cson_rc.RangeError; + break; + } + rc = f( state, ubuf, 12 ); + } + continue; + } + } + if( 0 == rc ) + { + rc = f(state, "\"", 1 ); + } + return rc; + } +} + +int cson_object_iter_init( cson_object const * obj, cson_object_iterator * iter ) +{ + if( ! obj || !iter ) return cson_rc.ArgError; + else + { + iter->obj = obj; + iter->pos = 0; + return 0; + } +} + +cson_kvp * cson_object_iter_next( cson_object_iterator * iter ) +{ + if( ! iter || !iter->obj ) return NULL; + else if( iter->pos >= iter->obj->kvp.count ) return NULL; + else + { + cson_kvp * rc = iter->obj->kvp.list[iter->pos++]; + while( (NULL==rc) && (iter->pos < iter->obj->kvp.count)) + { + rc = iter->obj->kvp.list[iter->pos++]; + } + return rc; + } +} + +static int cson_output_null( cson_data_dest_f f, void * state ) +{ + if( !f ) return cson_rc.ArgError; + else + { + return f(state, "null", 4); + } +} + +static int cson_output_bool( cson_value const * src, cson_data_dest_f f, void * state ) +{ + if( !f ) return cson_rc.ArgError; + else + { + char const v = cson_value_get_bool(src); + return f(state, v ? "true" : "false", v ? 4 : 5); + } +} + +static int cson_output_integer( cson_value const * src, cson_data_dest_f f, void * state ) +{ + if( !f ) return cson_rc.ArgError; + else if( !cson_value_is_integer(src) ) return cson_rc.TypeError; + else + { + enum { BufLen = 100 }; + char b[BufLen]; + int rc; + memset( b, 0, BufLen ); + rc = sprintf( b, "%"CSON_INT_T_PFMT, cson_value_get_integer(src) ) + /* Reminder: snprintf() is C99 */ + ; + return ( rc<=0 ) + ? cson_rc.RangeError + : f( state, b, (unsigned int)rc ) + ; + } +} + +static int cson_output_double( cson_value const * src, cson_data_dest_f f, void * state ) +{ + if( !f ) return cson_rc.ArgError; + else if( !cson_value_is_double(src) ) return cson_rc.TypeError; + else + { + enum { BufLen = 128 /* this must be relatively large or huge + doubles can cause us to overrun here, + resulting in stack-smashing errors. + */}; + char b[BufLen]; + int rc; + memset( b, 0, BufLen ); + rc = sprintf( b, "%"CSON_DOUBLE_T_PFMT, cson_value_get_double(src) ) + /* Reminder: snprintf() is C99 */ + ; + if( rc<=0 ) return cson_rc.RangeError; + else if(1) + { /* Strip trailing zeroes before passing it on... */ + unsigned int urc = (unsigned int)rc; + char * pos = b + urc - 1; + for( ; ('0' == *pos) && urc && (*(pos-1) != '.'); --pos, --urc ) + { + *pos = 0; + } + assert(urc && *pos); + return f( state, b, urc ); + } + else + { + unsigned int urc = (unsigned int)rc; + return f( state, b, urc ); + } + return 0; + } +} + +static int cson_output_string( cson_value const * src, char escapeFwdSlash, cson_data_dest_f f, void * state ) +{ + if( !f ) return cson_rc.ArgError; + else if( ! cson_value_is_string(src) ) return cson_rc.TypeError; + else + { + cson_string const * str = cson_value_get_string(src); + assert( NULL != str ); + return cson_str_to_json(cson_string_cstr(str), str->length, escapeFwdSlash, f, state); + } +} + + +/** + Outputs indention spacing to f(). + + blanks: (0)=no indentation, (1)=1 TAB per/level, (>1)=n spaces/level + + depth is the current depth of the output tree, and determines how much + indentation to generate. + + If blanks is 0 this is a no-op. Returns non-0 on error, and the + error code will always come from f(). +*/ +static int cson_output_indent( cson_data_dest_f f, void * state, + unsigned char blanks, unsigned int depth ) +{ + if( 0 == blanks ) return 0; + else + { +#if 0 + /* FIXME: stuff the indention into the buffer and make a single + call to f(). + */ + enum { BufLen = 200 }; + char buf[BufLen]; +#endif + unsigned int i; + unsigned int x; + char const ch = (1==blanks) ? '\t' : ' '; + int rc = f(state, "\n", 1 ); + for( i = 0; (i < depth) && (0 == rc); ++i ) + { + for( x = 0; (x < blanks) && (0 == rc); ++x ) + { + rc = f(state, &ch, 1); + } + } + return rc; + } +} + +static int cson_output_array( cson_value const * src, cson_data_dest_f f, void * state, + cson_output_opt const * fmt, unsigned int level ); +static int cson_output_object( cson_value const * src, cson_data_dest_f f, void * state, + cson_output_opt const * fmt, unsigned int level ); +/** + Main cson_output() implementation. Dispatches to a different impl depending + on src->api->typeID. + + Returns 0 on success. +*/ +static int cson_output_impl( cson_value const * src, cson_data_dest_f f, void * state, + cson_output_opt const * fmt, unsigned int level ) +{ + if( ! src || !f || !src->api ) return cson_rc.ArgError; + else + { + int rc = 0; + assert(fmt); + switch( src->api->typeID ) + { + case CSON_TYPE_UNDEF: + case CSON_TYPE_NULL: + rc = cson_output_null(f, state); + break; + case CSON_TYPE_BOOL: + rc = cson_output_bool(src, f, state); + break; + case CSON_TYPE_INTEGER: + rc = cson_output_integer(src, f, state); + break; + case CSON_TYPE_DOUBLE: + rc = cson_output_double(src, f, state); + break; + case CSON_TYPE_STRING: + rc = cson_output_string(src, fmt->escapeForwardSlashes, f, state); + break; + case CSON_TYPE_ARRAY: + rc = cson_output_array( src, f, state, fmt, level ); + break; + case CSON_TYPE_OBJECT: + rc = cson_output_object( src, f, state, fmt, level ); + break; + default: + rc = cson_rc.TypeError; + break; + } + return rc; + } +} + + +static int cson_output_array( cson_value const * src, cson_data_dest_f f, void * state, + cson_output_opt const * fmt, unsigned int level ) +{ + if( !src || !f || !fmt ) return cson_rc.ArgError; + else if( ! cson_value_is_array(src) ) return cson_rc.TypeError; + else if( level > fmt->maxDepth ) return cson_rc.RangeError; + else + { + int rc; + unsigned int i; + cson_value const * v; + char doIndent = fmt->indentation ? 1 : 0; + cson_array const * ar = cson_value_get_array(src); + assert( NULL != ar ); + if( 0 == ar->list.count ) + { + return f(state, "[]", 2 ); + } + else if( (1 == ar->list.count) && !fmt->indentSingleMemberValues ) doIndent = 0; + rc = f(state, "[", 1); + ++level; + if( doIndent ) + { + rc = cson_output_indent( f, state, fmt->indentation, level ); + } + for( i = 0; (i < ar->list.count) && (0 == rc); ++i ) + { + v = ar->list.list[i]; + if( v ) + { + rc = cson_output_impl( v, f, state, fmt, level ); + } + else + { + rc = cson_output_null( f, state ); + } + if( 0 == rc ) + { + if(i < (ar->list.count-1)) + { + rc = f(state, ",", 1); + if( 0 == rc ) + { + rc = doIndent + ? cson_output_indent( f, state, fmt->indentation, level ) + : 0 /*f( state, " ", 1 )*/; + } + } + } + } + --level; + if( doIndent && (0 == rc) ) + { + rc = cson_output_indent( f, state, fmt->indentation, level ); + } + return (0 == rc) + ? f(state, "]", 1) + : rc; + } +} + +static int cson_output_object( cson_value const * src, cson_data_dest_f f, void * state, + cson_output_opt const * fmt, unsigned int level ) +{ + if( !src || !f || !fmt ) return cson_rc.ArgError; + else if( ! cson_value_is_object(src) ) return cson_rc.TypeError; + else if( level > fmt->maxDepth ) return cson_rc.RangeError; + else + { + int rc; + unsigned int i; + cson_kvp const * kvp; + char doIndent = fmt->indentation ? 1 : 0; + cson_object const * obj = cson_value_get_object(src); + assert( (NULL != obj) && (NULL != fmt)); + if( 0 == obj->kvp.count ) + { + return f(state, "{}", 2 ); + } + else if( (1 == obj->kvp.count) && !fmt->indentSingleMemberValues ) doIndent = 0; + rc = f(state, "{", 1); + ++level; + if( doIndent ) + { + rc = cson_output_indent( f, state, fmt->indentation, level ); + } + for( i = 0; (i < obj->kvp.count) && (0 == rc); ++i ) + { + kvp = obj->kvp.list[i]; + if( kvp && kvp->key ) + { + cson_string const * sKey = cson_value_get_string(kvp->key); + char const * cKey = cson_string_cstr(sKey); + rc = cson_str_to_json(cKey, sKey->length, + fmt->escapeForwardSlashes, f, state); + if( 0 == rc ) + { + rc = fmt->addSpaceAfterColon + ? f(state, ": ", 2 ) + : f(state, ":", 1 ) + ; + } + if( 0 == rc) + { + rc = ( kvp->value ) + ? cson_output_impl( kvp->value, f, state, fmt, level ) + : cson_output_null( f, state ); + } + } + else + { + assert( 0 && "Possible internal error." ); + continue /* internal error? */; + } + if( 0 == rc ) + { + if(i < (obj->kvp.count-1)) + { + rc = f(state, ",", 1); + if( 0 == rc ) + { + rc = doIndent + ? cson_output_indent( f, state, fmt->indentation, level ) + : 0 /*f( state, " ", 1 )*/; + } + } + } + } + --level; + if( doIndent && (0 == rc) ) + { + rc = cson_output_indent( f, state, fmt->indentation, level ); + } + return (0 == rc) + ? f(state, "}", 1) + : rc; + } +} + +int cson_output( cson_value const * src, cson_data_dest_f f, + void * state, cson_output_opt const * fmt ) +{ + int rc; + if(! fmt ) fmt = &cson_output_opt_empty; + rc = cson_output_impl(src, f, state, fmt, 0 ); + if( (0 == rc) && fmt->addNewline ) + { + rc = f(state, "\n", 1); + } + return rc; +} + +int cson_data_dest_FILE( void * state, void const * src, unsigned int n ) +{ + if( ! state ) return cson_rc.ArgError; + else if( !src || !n ) return 0; + else + { + return ( 1 == fwrite( src, n, 1, (FILE*) state ) ) + ? 0 + : cson_rc.IOError; + } +} + +int cson_output_FILE( cson_value const * src, FILE * dest, cson_output_opt const * fmt ) +{ + int rc = 0; + if( fmt ) + { + rc = cson_output( src, cson_data_dest_FILE, dest, fmt ); + } + else + { + /* We normally want a newline on FILE output. */ + cson_output_opt opt = cson_output_opt_empty; + opt.addNewline = 1; + rc = cson_output( src, cson_data_dest_FILE, dest, &opt ); + } + if( 0 == rc ) + { + fflush( dest ); + } + return rc; +} + +int cson_output_filename( cson_value const * src, char const * dest, cson_output_opt const * fmt ) +{ + if( !src || !dest ) return cson_rc.ArgError; + else + { + FILE * f = fopen(dest,"wb"); + if( !f ) return cson_rc.IOError; + else + { + int const rc = cson_output_FILE( src, f, fmt ); + fclose(f); + return rc; + } + } +} + +int cson_parse_filename( cson_value ** tgt, char const * src, + cson_parse_opt const * opt, cson_parse_info * err ) +{ + if( !src || !tgt ) return cson_rc.ArgError; + else + { + FILE * f = fopen(src, "r"); + if( !f ) return cson_rc.IOError; + else + { + int const rc = cson_parse_FILE( tgt, f, opt, err ); + fclose(f); + return rc; + } + } +} + +/** Internal type to hold state for a JSON input string. + */ +typedef struct cson_data_source_StringSource_ +{ + /** Start of input string. */ + char const * str; + /** Current iteration position. Must initially be == str. */ + char const * pos; + /** Logical EOF, one-past-the-end of str. */ + char const * end; +} cson_data_source_StringSource_t; + +/** + A cson_data_source_f() implementation which requires the state argument + to be a properly populated (cson_data_source_StringSource_t*). +*/ +static int cson_data_source_StringSource( void * state, void * dest, unsigned int * n ) +{ + if( !state || !n || !dest ) return cson_rc.ArgError; + else if( !*n ) return 0 /* ignore this */; + else + { + unsigned int i; + cson_data_source_StringSource_t * ss = (cson_data_source_StringSource_t*) state; + unsigned char * tgt = (unsigned char *)dest; + for( i = 0; (i < *n) && (ss->pos < ss->end); ++i, ++ss->pos, ++tgt ) + { + *tgt = *ss->pos; + } + *n = i; + return 0; + } +} + +int cson_parse_string( cson_value ** tgt, char const * src, unsigned int len, + cson_parse_opt const * opt, cson_parse_info * err ) +{ + if( ! tgt || !src ) return cson_rc.ArgError; + else if( !*src || (len<2/*2==len of {} and []*/) ) return cson_rc.RangeError; + else + { + cson_data_source_StringSource_t ss; + ss.str = ss.pos = src; + ss.end = src + len; + return cson_parse( tgt, cson_data_source_StringSource, &ss, opt, err ); + } + +} + +int cson_parse_buffer( cson_value ** tgt, + cson_buffer const * buf, + cson_parse_opt const * opt, + cson_parse_info * err ) +{ + return ( !tgt || !buf || !buf->mem || !buf->used ) + ? cson_rc.ArgError + : cson_parse_string( tgt, (char const *)buf->mem, + buf->used, opt, err ); +} + +int cson_buffer_reserve( cson_buffer * buf, cson_size_t n ) +{ + if( ! buf ) return cson_rc.ArgError; + else if( 0 == n ) + { + cson_free(buf->mem, "cson_buffer::mem"); + *buf = cson_buffer_empty; + return 0; + } + else if( buf->capacity >= n ) + { + return 0; + } + else + { + unsigned char * x = (unsigned char *)cson_realloc( buf->mem, n, "cson_buffer::mem" ); + if( ! x ) return cson_rc.AllocError; + memset( x + buf->used, 0, n - buf->used ); + buf->mem = x; + buf->capacity = n; + ++buf->timesExpanded; + return 0; + } +} + +cson_size_t cson_buffer_fill( cson_buffer * buf, char c ) +{ + if( !buf || !buf->capacity || !buf->mem ) return 0; + else + { + memset( buf->mem, c, buf->capacity ); + return buf->capacity; + } +} + +/** + cson_data_dest_f() implementation, used by cson_output_buffer(). + + arg MUST be a (cson_buffer*). This function appends n bytes at + position arg->used, expanding the buffer as necessary. +*/ +static int cson_data_dest_cson_buffer( void * arg, void const * data_, unsigned int n ) +{ + if( !arg ) return cson_rc.ArgError; + else if( ! n ) return 0; + else + { + cson_buffer * sb = (cson_buffer*)arg; + char const * data = (char const *)data_; + cson_size_t npos = sb->used + n; + unsigned int i; + if( npos >= sb->capacity ) + { + const cson_size_t oldCap = sb->capacity; + const cson_size_t asz = npos * 2; + if( asz < npos ) return cson_rc.ArgError; /* overflow */ + else if( 0 != cson_buffer_reserve( sb, asz ) ) return cson_rc.AllocError; + assert( (sb->capacity > oldCap) && "Internal error in memory buffer management!" ); + /* make sure it gets NUL terminated. */ + memset( sb->mem + oldCap, 0, (sb->capacity - oldCap) ); + } + for( i = 0; i < n; ++i, ++sb->used ) + { + sb->mem[sb->used] = data[i]; + } + return 0; + } +} + + +int cson_output_buffer( cson_value const * v, cson_buffer * buf, + cson_output_opt const * opt ) +{ + int rc = cson_output( v, cson_data_dest_cson_buffer, buf, opt ); + if( 0 == rc ) + { /* Ensure that the buffer is null-terminated. */ + rc = cson_buffer_reserve( buf, buf->used + 1 ); + if( 0 == rc ) + { + buf->mem[buf->used] = 0; + } + } + return rc; +} + +/** @internal + +Tokenizes an input string on a given separator. Inputs are: + +- (inp) = is a pointer to the pointer to the start of the input. + +- (separator) = the separator character + +- (end) = a pointer to NULL. i.e. (*end == NULL) + +This function scans *inp for the given separator char or a NUL char. +Successive separators at the start of *inp are skipped. The effect is +that, when this function is called in a loop, all neighboring +separators are ignored. e.g. the string "aa.bb...cc" will tokenize to +the list (aa,bb,cc) if the separator is '.' and to (aa.,...cc) if the +separator is 'b'. + +Returns 0 (false) if it finds no token, else non-0 (true). + +Output: + +- (*inp) will be set to the first character of the next token. + +- (*end) will point to the one-past-the-end point of the token. + +If (*inp == *end) then the end of the string has been reached +without finding a token. + +Post-conditions: + +- (*end == *inp) if no token is found. + +- (*end > *inp) if a token is found. + +It is intolerant of NULL values for (inp, end), and will assert() in +debug builds if passed NULL as either parameter. +*/ +static char cson_next_token( char const ** inp, char separator, char const ** end ) +{ + char const * pos = NULL; + assert( inp && end && *inp ); + if( *inp == *end ) return 0; + pos = *inp; + if( !*pos ) + { + *end = pos; + return 0; + } + for( ; *pos && (*pos == separator); ++pos) { /* skip preceeding splitters */ } + *inp = pos; + for( ; *pos && (*pos != separator); ++pos) { /* find next splitter */ } + *end = pos; + return (pos > *inp) ? 1 : 0; +} + +int cson_object_fetch_sub2( cson_object const * obj, cson_value ** tgt, char const * path ) +{ + if( ! obj || !path ) return cson_rc.ArgError; + else if( !*path || !*(1+path) ) return cson_rc.RangeError; + else return cson_object_fetch_sub(obj, tgt, path+1, *path); +} + +int cson_object_fetch_sub( cson_object const * obj, cson_value ** tgt, char const * path, char sep ) +{ + if( ! obj || !path ) return cson_rc.ArgError; + else if( !*path || !sep ) return cson_rc.RangeError; + else + { + char const * beg = path; + char const * end = NULL; + int rc; + unsigned int i, len; + unsigned int tokenCount = 0; + cson_value * cv = NULL; + cson_object const * curObj = obj; + enum { BufSize = 128 }; + char buf[BufSize]; + memset( buf, 0, BufSize ); + + while( cson_next_token( &beg, sep, &end ) ) + { + if( beg == end ) break; + else + { + ++tokenCount; + beg = end; + end = NULL; + } + } + if( 0 == tokenCount ) return cson_rc.RangeError; + beg = path; + end = NULL; + for( i = 0; i < tokenCount; ++i, beg=end, end=NULL ) + { + rc = cson_next_token( &beg, sep, &end ); + assert( 1 == rc ); + assert( beg != end ); + assert( end > beg ); + len = end - beg; + if( len > (BufSize-1) ) return cson_rc.RangeError; + memset( buf, 0, len + 1 ); + memcpy( buf, beg, len ); + buf[len] = 0; + cv = cson_object_get( curObj, buf ); + if( NULL == cv ) return cson_rc.NotFoundError; + else if( i == (tokenCount-1) ) + { + if(tgt) *tgt = cv; + return 0; + } + else if( cson_value_is_object(cv) ) + { + curObj = cson_value_get_object(cv); + assert((NULL != curObj) && "Detected mis-management of internal memory!"); + } + /* TODO: arrays. Requires numeric parsing for the index. */ + else + { + return cson_rc.NotFoundError; + } + } + assert( i == tokenCount ); + return cson_rc.NotFoundError; + } +} + +cson_value * cson_object_get_sub( cson_object const * obj, char const * path, char sep ) +{ + cson_value * v = NULL; + cson_object_fetch_sub( obj, &v, path, sep ); + return v; +} + +cson_value * cson_object_get_sub2( cson_object const * obj, char const * path ) +{ + cson_value * v = NULL; + cson_object_fetch_sub2( obj, &v, path ); + return v; +} + + +/** + If v is-a Object or Array then this function returns a deep + clone, otherwise it returns v. In either case, the refcount + of the returned value is increased by 1 by this call. +*/ +static cson_value * cson_value_clone_ref( cson_value * v ) +{ + cson_value * rc = NULL; +#define TRY_SHARING 1 +#if TRY_SHARING + if(!v ) return rc; + else if( cson_value_is_object(v) + || cson_value_is_array(v)) + { + rc = cson_value_clone( v ); + } + else + { + rc = v; + } +#else + rc = cson_value_clone(v); +#endif +#undef TRY_SHARING + cson_value_add_reference(rc); + return rc; +} + +static cson_value * cson_value_clone_array( cson_value const * orig ) +{ + unsigned int i = 0; + cson_array const * asrc = cson_value_get_array( orig ); + unsigned int alen = cson_array_length_get( asrc ); + cson_value * destV = NULL; + cson_array * destA = NULL; + assert( orig && asrc ); + destV = cson_value_new_array(); + if( NULL == destV ) return NULL; + destA = cson_value_get_array( destV ); + assert( destA ); + if( 0 != cson_array_reserve( destA, alen ) ) + { + cson_value_free( destV ); + return NULL; + } + for( ; i < alen; ++i ) + { + cson_value * ch = cson_array_get( asrc, i ); + if( NULL != ch ) + { + cson_value * cl = cson_value_clone_ref( ch ); + if( NULL == cl ) + { + cson_value_free( destV ); + return NULL; + } + if( 0 != cson_array_set( destA, i, cl ) ) + { + cson_value_free( cl ); + cson_value_free( destV ); + return NULL; + } + cson_value_free(cl)/*remove our artificial reference */; + } + } + return destV; +} + +static cson_value * cson_value_clone_object( cson_value const * orig ) +{ + cson_object const * src = cson_value_get_object( orig ); + cson_value * destV = NULL; + cson_object * dest = NULL; + cson_kvp const * kvp = NULL; + cson_object_iterator iter = cson_object_iterator_empty; + assert( orig && src ); + if( 0 != cson_object_iter_init( src, &iter ) ) + { + return NULL; + } + destV = cson_value_new_object(); + if( NULL == destV ) return NULL; + dest = cson_value_get_object( destV ); + assert( dest ); + if( src->kvp.count > cson_kvp_list_reserve( &dest->kvp, src->kvp.count ) ){ + cson_value_free( destV ); + return NULL; + } + while( (kvp = cson_object_iter_next( &iter )) ) + { + cson_value * key = NULL; + cson_value * val = NULL; + assert( kvp->key && (kvp->key->refcount>0) ); + key = cson_value_clone_ref(kvp->key); + val = key ? cson_value_clone_ref(kvp->value) : NULL; + if( ! key || !val ){ + goto error; + } + assert( CSON_STR(key) ); + if( 0 != cson_object_set_s( dest, CSON_STR(key), val ) ) + { + goto error; + } + /* remove our references */ + cson_value_free(key); + cson_value_free(val); + continue; + error: + cson_value_free(key); + cson_value_free(val); + cson_value_free(destV); + destV = NULL; + break; + } + return destV; +} + +cson_value * cson_value_clone( cson_value const * orig ) +{ + if( NULL == orig ) return NULL; + else + { + switch( orig->api->typeID ) + { + case CSON_TYPE_UNDEF: + assert(0 && "This should never happen."); + return NULL; + case CSON_TYPE_NULL: + return cson_value_null(); + case CSON_TYPE_BOOL: + return cson_value_new_bool( cson_value_get_bool( orig ) ); + case CSON_TYPE_INTEGER: + return cson_value_new_integer( cson_value_get_integer( orig ) ); + break; + case CSON_TYPE_DOUBLE: + return cson_value_new_double( cson_value_get_double( orig ) ); + break; + case CSON_TYPE_STRING: { + cson_string const * str = cson_value_get_string( orig ); + return cson_value_new_string( cson_string_cstr( str ), + cson_string_length_bytes( str ) ); + } + case CSON_TYPE_ARRAY: + return cson_value_clone_array( orig ); + case CSON_TYPE_OBJECT: + return cson_value_clone_object( orig ); + } + assert( 0 && "We can't get this far." ); + return NULL; + } +} + +cson_value * cson_string_value(cson_string const * s) +{ +#define MT CSON_SPECIAL_VALUES[CSON_VAL_STR_EMPTY] + return s + ? ((s==MT.value) ? &MT : CSON_VCAST(s)) + : NULL; +#undef MT +} + +cson_value * cson_object_value(cson_object const * s) +{ + return s + ? CSON_VCAST(s) + : NULL; +} + + +cson_value * cson_array_value(cson_array const * s) +{ + return s + ? CSON_VCAST(s) + : NULL; +} + +void cson_free_object(cson_object *x) +{ + if(x) cson_value_free(cson_object_value(x)); +} +void cson_free_array(cson_array *x) +{ + if(x) cson_value_free(cson_array_value(x)); +} + +void cson_free_string(cson_string *x) +{ + if(x) cson_value_free(cson_string_value(x)); +} +void cson_free_value(cson_value *x) +{ + if(x) cson_value_free(x); +} + + +#if 0 +/* i'm not happy with this... */ +char * cson_pod_to_string( cson_value const * orig ) +{ + if( ! orig ) return NULL; + else + { + enum { BufSize = 64 }; + char * v = NULL; + switch( orig->api->typeID ) + { + case CSON_TYPE_BOOL: { + char const bv = cson_value_get_bool(orig); + v = cson_strdup( bv ? "true" : "false", + bv ? 4 : 5 ); + break; + } + case CSON_TYPE_UNDEF: + case CSON_TYPE_NULL: { + v = cson_strdup( "null", 4 ); + break; + } + case CSON_TYPE_STRING: { + cson_string const * jstr = cson_value_get_string(orig); + unsigned const int slen = cson_string_length_bytes( jstr ); + assert( NULL != jstr ); + v = cson_strdup( cson_string_cstr( jstr ), slen ); + break; + } + case CSON_TYPE_INTEGER: { + char buf[BufSize] = {0}; + if( 0 < sprintf( v, "%"CSON_INT_T_PFMT, cson_value_get_integer(orig)) ) + { + v = cson_strdup( buf, strlen(buf) ); + } + break; + } + case CSON_TYPE_DOUBLE: { + char buf[BufSize] = {0}; + if( 0 < sprintf( v, "%"CSON_DOUBLE_T_PFMT, cson_value_get_double(orig)) ) + { + v = cson_strdup( buf, strlen(buf) ); + } + break; + } + default: + break; + } + return v; + } +} +#endif + +#if 0 +/* i'm not happy with this... */ +char * cson_pod_to_string( cson_value const * orig ) +{ + if( ! orig ) return NULL; + else + { + enum { BufSize = 64 }; + char * v = NULL; + switch( orig->api->typeID ) + { + case CSON_TYPE_BOOL: { + char const bv = cson_value_get_bool(orig); + v = cson_strdup( bv ? "true" : "false", + bv ? 4 : 5 ); + break; + } + case CSON_TYPE_UNDEF: + case CSON_TYPE_NULL: { + v = cson_strdup( "null", 4 ); + break; + } + case CSON_TYPE_STRING: { + cson_string const * jstr = cson_value_get_string(orig); + unsigned const int slen = cson_string_length_bytes( jstr ); + assert( NULL != jstr ); + v = cson_strdup( cson_string_cstr( jstr ), slen ); + break; + } + case CSON_TYPE_INTEGER: { + char buf[BufSize] = {0}; + if( 0 < sprintf( v, "%"CSON_INT_T_PFMT, cson_value_get_integer(orig)) ) + { + v = cson_strdup( buf, strlen(buf) ); + } + break; + } + case CSON_TYPE_DOUBLE: { + char buf[BufSize] = {0}; + if( 0 < sprintf( v, "%"CSON_DOUBLE_T_PFMT, cson_value_get_double(orig)) ) + { + v = cson_strdup( buf, strlen(buf) ); + } + break; + } + default: + break; + } + return v; + } +} +#endif + +unsigned int cson_value_msize(cson_value const * v) +{ + if(!v) return 0; + else if( cson_value_is_builtin(v) ) return 0; + else { + unsigned int rc = sizeof(cson_value); + assert(NULL != v->api); + switch(v->api->typeID){ + case CSON_TYPE_INTEGER: + assert( v != &CSON_SPECIAL_VALUES[CSON_VAL_INT_0]); + rc += sizeof(cson_int_t); + break; + case CSON_TYPE_DOUBLE: + assert( v != &CSON_SPECIAL_VALUES[CSON_VAL_DBL_0]); + rc += sizeof(cson_double_t); + break; + case CSON_TYPE_STRING: + rc += sizeof(cson_string) + + CSON_STR(v)->length + 1/*NUL*/; + break; + case CSON_TYPE_ARRAY:{ + cson_array const * ar = CSON_ARRAY(v); + cson_value_list const * li; + unsigned int i = 0; + assert( NULL != ar ); + li = &ar->list; + rc += sizeof(cson_array) + + (li->alloced * sizeof(cson_value *)); + for( ; i < li->count; ++i ){ + cson_value const * e = ar->list.list[i]; + if( e ) rc += cson_value_msize( e ); + } + break; + } + case CSON_TYPE_OBJECT:{ + cson_object const * obj = CSON_OBJ(v); + unsigned int i = 0; + cson_kvp_list const * kl; + assert(NULL != obj); + kl = &obj->kvp; + rc += sizeof(cson_object) + + (kl->alloced * sizeof(cson_kvp*)); + for( ; i < kl->count; ++i ){ + cson_kvp const * kvp = kl->list[i]; + assert(NULL != kvp); + rc += cson_value_msize(kvp->key); + rc += cson_value_msize(kvp->value); + } + break; + } + case CSON_TYPE_UNDEF: + case CSON_TYPE_NULL: + case CSON_TYPE_BOOL: + assert( 0 && "Should have been caught by is-builtin check!" ); + break; + default: + assert(0 && "Invalid typeID!"); + return 0; +#undef RCCHECK + } + return rc; + } +} + +int cson_object_merge( cson_object * dest, cson_object const * src, int flags ){ + cson_object_iterator iter = cson_object_iterator_empty; + int rc; + char const replace = (flags & CSON_MERGE_REPLACE); + char const recurse = !(flags & CSON_MERGE_NO_RECURSE); + cson_kvp const * kvp; + if((!dest || !src) || (dest==src)) return cson_rc.ArgError; + rc = cson_object_iter_init( src, &iter ); + if(rc) return rc; + while( (kvp = cson_object_iter_next(&iter) ) ) + { + cson_string * key = cson_kvp_key(kvp); + cson_value * val = cson_kvp_value(kvp); + cson_value * check = cson_object_get_s( dest, key ); + if(!check){ + cson_object_set_s( dest, key, val ); + continue; + } + else if(!replace && !recurse) continue; + else if(replace && !recurse){ + cson_object_set_s( dest, key, val ); + continue; + } + else if( recurse ){ + if( cson_value_is_object(check) && + cson_value_is_object(val) ){ + rc = cson_object_merge( cson_value_get_object(check), + cson_value_get_object(val), + flags ); + if(rc) return rc; + else continue; + } + else continue; + } + else continue; + } + return 0; +} + +static cson_value * cson_guess_arg_type(char const *arg){ + char * end = NULL; + if(!arg || !*arg) return cson_value_null(); + else if(('0'>*arg) || ('9'<*arg)){ + goto do_string; + } + else{ /* try numbers... */ + long const val = strtol(arg, &end, 10); + if(!*end){ + return cson_value_new_integer( (cson_int_t)val); + } + else if( '.' != *end ) { + goto do_string; + } + else { + double const val = strtod(arg, &end); + if(!*end){ + return cson_value_new_double(val); + } + } + } + do_string: + return cson_value_new_string(arg, strlen(arg)); +} + + +int cson_parse_argv_flags( int argc, char const * const * argv, + cson_object ** tgt, unsigned int * count ){ + cson_object * o = NULL; + int rc = 0; + int i = 0; + if(argc<1 || !argc || !tgt) return cson_rc.ArgError; + o = *tgt ? *tgt : cson_new_object(); + if(count) *count = 0; + for( i = 0; i < argc; ++i ){ + char const * arg = argv[i]; + char const * key = arg; + char const * pos; + cson_string * k = NULL; + cson_value * v = NULL; + if('-' != *arg) continue; + while('-'==*key) ++key; + if(!*key) continue; + pos = key; + while( *pos && ('=' != *pos)) ++pos; + k = cson_new_string(key, pos-key); + if(!k){ + rc = cson_rc.AllocError; + break; + } + if(!*pos){ /** --key */ + v = cson_value_true(); + }else{ /** --key=...*/ + assert('=' == *pos); + ++pos /*skip '='*/; + v = cson_guess_arg_type(pos); + } + if(0 != (rc=cson_object_set_s(o, k, v))){ + cson_free_string(k); + cson_value_free(v); + break; + } + else if(count) ++*count; + } + if(o != *tgt){ + if(rc) cson_free_object(o); + else *tgt = o; + } + return rc; +} + +#if defined(__cplusplus) +} /*extern "C"*/ +#endif + +#undef MARKER +#undef CSON_OBJECT_PROPS_SORT +#undef CSON_OBJECT_PROPS_SORT_USE_LENGTH +#undef CSON_CAST +#undef CSON_INT +#undef CSON_DBL +#undef CSON_STR +#undef CSON_OBJ +#undef CSON_ARRAY +#undef CSON_VCAST +#undef CSON_MALLOC_IMPL +#undef CSON_FREE_IMPL +#undef CSON_REALLOC_IMPL +/* end file ./cson.c */ +/* begin file ./cson_lists.h */ +/* Auto-generated from cson_list.h. Edit at your own risk! */ +unsigned int cson_value_list_reserve( cson_value_list * self, unsigned int n ) +{ + if( !self ) return 0; + else if(0 == n) + { + if(0 == self->alloced) return 0; + cson_free(self->list, "cson_value_list_reserve"); + self->list = NULL; + self->alloced = self->count = 0; + return 0; + } + else if( self->alloced >= n ) + { + return self->alloced; + } + else + { + size_t const sz = sizeof(cson_value *) * n; + cson_value * * m = (cson_value **)cson_realloc( self->list, sz, "cson_value_list_reserve" ); + if( ! m ) return self->alloced; + + memset( m + self->alloced, 0, (sizeof(cson_value *)*(n-self->alloced))); + self->alloced = n; + self->list = m; + return n; + } +} +int cson_value_list_append( cson_value_list * self, cson_value * cp ) +{ + if( !self || !cp ) return cson_rc.ArgError; + else if( self->alloced > cson_value_list_reserve(self, self->count+1) ) + { + return cson_rc.AllocError; + } + else + { + self->list[self->count++] = cp; + return 0; + } +} +int cson_value_list_visit( cson_value_list * self, + + int (*visitor)(cson_value * obj, void * visitorState ), + + + + void * visitorState ) +{ + int rc = cson_rc.ArgError; + if( self && visitor ) + { + unsigned int i = 0; + for( rc = 0; (i < self->count) && (0 == rc); ++i ) + { + + cson_value * obj = self->list[i]; + + + + if(obj) rc = visitor( obj, visitorState ); + } + } + return rc; +} +void cson_value_list_clean( cson_value_list * self, + + void (*cleaner)(cson_value * obj) + + + + ) +{ + if( self && cleaner && self->count ) + { + unsigned int i = 0; + for( ; i < self->count; ++i ) + { + + cson_value * obj = self->list[i]; + + + + if(obj) cleaner(obj); + } + } + cson_value_list_reserve(self,0); +} +unsigned int cson_kvp_list_reserve( cson_kvp_list * self, unsigned int n ) +{ + if( !self ) return 0; + else if(0 == n) + { + if(0 == self->alloced) return 0; + cson_free(self->list, "cson_kvp_list_reserve"); + self->list = NULL; + self->alloced = self->count = 0; + return 0; + } + else if( self->alloced >= n ) + { + return self->alloced; + } + else + { + size_t const sz = sizeof(cson_kvp *) * n; + cson_kvp * * m = (cson_kvp **)cson_realloc( self->list, sz, "cson_kvp_list_reserve" ); + if( ! m ) return self->alloced; + + memset( m + self->alloced, 0, (sizeof(cson_kvp *)*(n-self->alloced))); + self->alloced = n; + self->list = m; + return n; + } +} +int cson_kvp_list_append( cson_kvp_list * self, cson_kvp * cp ) +{ + if( !self || !cp ) return cson_rc.ArgError; + else if( self->alloced > cson_kvp_list_reserve(self, self->count+1) ) + { + return cson_rc.AllocError; + } + else + { + self->list[self->count++] = cp; + return 0; + } +} +int cson_kvp_list_visit( cson_kvp_list * self, + + int (*visitor)(cson_kvp * obj, void * visitorState ), + + + + void * visitorState ) +{ + int rc = cson_rc.ArgError; + if( self && visitor ) + { + unsigned int i = 0; + for( rc = 0; (i < self->count) && (0 == rc); ++i ) + { + + cson_kvp * obj = self->list[i]; + + + + if(obj) rc = visitor( obj, visitorState ); + } + } + return rc; +} +void cson_kvp_list_clean( cson_kvp_list * self, + + void (*cleaner)(cson_kvp * obj) + + + + ) +{ + if( self && cleaner && self->count ) + { + unsigned int i = 0; + for( ; i < self->count; ++i ) + { + + cson_kvp * obj = self->list[i]; + + + + if(obj) cleaner(obj); + } + } + cson_kvp_list_reserve(self,0); +} +/* end file ./cson_lists.h */ +/* begin file ./cson_sqlite3.c */ +/** @file cson_sqlite3.c + +This file contains the implementation code for the cson +sqlite3-to-JSON API. + +License: the same as the cson core library. + +Author: Stephan Beal (http://wanderinghorse.net/home/stephan) +*/ +#if CSON_ENABLE_SQLITE3 /* we do this here for the sake of the amalgamation build */ +#include +#include /* strlen() */ + +#if 0 +#include +#define MARKER if(1) printf("MARKER: %s:%d:%s():\t",__FILE__,__LINE__,__func__); if(1) printf +#else +#define MARKER if(0) printf +#endif + +#if defined(__cplusplus) +extern "C" { +#endif + +cson_value * cson_sqlite3_column_to_value( sqlite3_stmt * st, int col ) +{ + if( ! st ) return NULL; + else + { +#if 0 + sqlite3_value * val = sqlite3_column_type(st,col); + int const vtype = val ? sqlite3_value_type(val) : -1; + if( ! val ) return cson_value_null(); +#else + int const vtype = sqlite3_column_type(st,col); +#endif + switch( vtype ) + { + case SQLITE_NULL: + return cson_value_null(); + case SQLITE_INTEGER: + /* FIXME: for large integers fall back to Double instead. */ + return cson_value_new_integer( (cson_int_t) sqlite3_column_int64(st, col) ); + case SQLITE_FLOAT: + return cson_value_new_double( sqlite3_column_double(st, col) ); + case SQLITE_BLOB: /* arguably fall through... */ + case SQLITE_TEXT: { + char const * str = (char const *)sqlite3_column_text(st,col); + return cson_value_new_string(str, str ? strlen(str) : 0); + } + default: + return NULL; + } + } +} + +cson_value * cson_sqlite3_column_names( sqlite3_stmt * st ) +{ + cson_value * aryV = NULL; + cson_array * ary = NULL; + char const * colName = NULL; + int i = 0; + int rc = 0; + int colCount = 0; + assert(st); + colCount = sqlite3_column_count(st); + if( colCount <= 0 ) return NULL; + + aryV = cson_value_new_array(); + if( ! aryV ) return NULL; + ary = cson_value_get_array(aryV); + assert(ary); + for( i = 0; (0 ==rc) && (i < colCount); ++i ) + { + colName = sqlite3_column_name( st, i ); + if( ! colName ) rc = cson_rc.AllocError; + else + { + rc = cson_array_set( ary, (unsigned int)i, + cson_value_new_string(colName, strlen(colName)) ); + } + } + if( 0 == rc ) return aryV; + else + { + cson_value_free(aryV); + return NULL; + } +} + + +cson_value * cson_sqlite3_row_to_object2( sqlite3_stmt * st, + cson_array * colNames ) +{ + cson_value * rootV = NULL; + cson_object * root = NULL; + cson_string * colName = NULL; + int i = 0; + int rc = 0; + cson_value * currentValue = NULL; + int const colCount = sqlite3_column_count(st); + if( !colCount || (colCount>cson_array_length_get(colNames)) ) { + return NULL; + } + rootV = cson_value_new_object(); + if(!rootV) return NULL; + root = cson_value_get_object(rootV); + for( i = 0; i < colCount; ++i ) + { + colName = cson_value_get_string( cson_array_get( colNames, i ) ); + if( ! colName ) goto error; + currentValue = cson_sqlite3_column_to_value(st,i); + if( ! currentValue ) currentValue = cson_value_null(); + rc = cson_object_set_s( root, colName, currentValue ); + if( 0 != rc ) + { + cson_value_free( currentValue ); + goto error; + } + } + goto end; + error: + cson_value_free( rootV ); + rootV = NULL; + end: + return rootV; +} + + +cson_value * cson_sqlite3_row_to_object( sqlite3_stmt * st ) +{ +#if 0 + cson_value * arV = cson_sqlite3_column_names(st); + cson_array * ar = NULL; + cson_value * rc = NULL; + if(!arV) return NULL; + ar = cson_value_get_array(arV); + assert( NULL != ar ); + rc = cson_sqlite3_row_to_object2(st, ar); + cson_value_free(arV); + return rc; +#else + cson_value * rootV = NULL; + cson_object * root = NULL; + char const * colName = NULL; + int i = 0; + int rc = 0; + cson_value * currentValue = NULL; + int const colCount = sqlite3_column_count(st); + if( !colCount ) return NULL; + rootV = cson_value_new_object(); + if(!rootV) return NULL; + root = cson_value_get_object(rootV); + for( i = 0; i < colCount; ++i ) + { + colName = sqlite3_column_name( st, i ); + if( ! colName ) goto error; + currentValue = cson_sqlite3_column_to_value(st,i); + if( ! currentValue ) currentValue = cson_value_null(); + rc = cson_object_set( root, colName, currentValue ); + if( 0 != rc ) + { + cson_value_free( currentValue ); + goto error; + } + } + goto end; + error: + cson_value_free( rootV ); + rootV = NULL; + end: + return rootV; +#endif +} + +cson_value * cson_sqlite3_row_to_array( sqlite3_stmt * st ) +{ + cson_value * aryV = NULL; + cson_array * ary = NULL; + int i = 0; + int rc = 0; + int const colCount = sqlite3_column_count(st); + if( ! colCount ) return NULL; + aryV = cson_value_new_array(); + if( ! aryV ) return NULL; + ary = cson_value_get_array(aryV); + rc = cson_array_reserve(ary, (unsigned int) colCount ); + if( 0 != rc ) goto error; + + for( i = 0; i < colCount; ++i ){ + cson_value * elem = cson_sqlite3_column_to_value(st,i); + if( ! elem ) goto error; + rc = cson_array_append(ary,elem); + if(0!=rc) + { + cson_value_free( elem ); + goto end; + } + } + goto end; + error: + cson_value_free(aryV); + aryV = NULL; + end: + return aryV; +} + + +/** + Internal impl of cson_sqlite3_stmt_to_json() when the 'fat' + parameter is non-0. +*/ +static int cson_sqlite3_stmt_to_json_fat( sqlite3_stmt * st, cson_value ** tgt ) +{ +#define RETURN(RC) { if(rootV) cson_value_free(rootV); return RC; } + if( ! tgt || !st ) return cson_rc.ArgError; + else + { + cson_value * rootV = NULL; + cson_object * root = NULL; + cson_value * colsV = NULL; + cson_array * cols = NULL; + cson_value * rowsV = NULL; + cson_array * rows = NULL; + cson_value * objV = NULL; + int rc = 0; + int const colCount = sqlite3_column_count(st); + if( colCount <= 0 ) return cson_rc.ArgError; + rootV = cson_value_new_object(); + if( ! rootV ) return cson_rc.AllocError; + colsV = cson_sqlite3_column_names(st); + if( ! colsV ) + { + cson_value_free( rootV ); + RETURN(cson_rc.AllocError); + } + cols = cson_value_get_array(colsV); + assert(NULL != cols); + root = cson_value_get_object(rootV); + rc = cson_object_set( root, "columns", colsV ); + if( rc ) + { + cson_value_free( colsV ); + RETURN(rc); + } + rowsV = cson_value_new_array(); + if( ! rowsV ) RETURN(cson_rc.AllocError); + rc = cson_object_set( root, "rows", rowsV ); + if( rc ) + { + cson_value_free( rowsV ); + RETURN(rc); + } + rows = cson_value_get_array(rowsV); + assert(rows); + while( SQLITE_ROW == sqlite3_step(st) ) + { + objV = cson_sqlite3_row_to_object2(st, cols); + if( ! objV ) RETURN(cson_rc.UnknownError); + rc = cson_array_append( rows, objV ); + if( rc ) + { + cson_value_free( objV ); + RETURN(rc); + } + } + *tgt = rootV; + return 0; + } +#undef RETURN +} + +/** + Internal impl of cson_sqlite3_stmt_to_json() when the 'fat' + parameter is 0. +*/ +static int cson_sqlite3_stmt_to_json_slim( sqlite3_stmt * st, cson_value ** tgt ) +{ +#define RETURN(RC) { if(rootV) cson_value_free(rootV); return RC; } + if( ! tgt || !st ) return cson_rc.ArgError; + else + { + cson_value * rootV = NULL; + cson_object * root = NULL; + cson_value * aryV = NULL; + cson_value * rowsV = NULL; + cson_array * rows = NULL; + int rc = 0; + int const colCount = sqlite3_column_count(st); + if( colCount <= 0 ) return cson_rc.ArgError; + rootV = cson_value_new_object(); + if( ! rootV ) return cson_rc.AllocError; + aryV = cson_sqlite3_column_names(st); + if( ! aryV ) + { + cson_value_free( rootV ); + RETURN(cson_rc.AllocError); + } + root = cson_value_get_object(rootV); + rc = cson_object_set( root, "columns", aryV ); + if( rc ) + { + cson_value_free( aryV ); + RETURN(rc); + } + aryV = NULL; + rowsV = cson_value_new_array(); + if( ! rowsV ) RETURN(cson_rc.AllocError); + rc = cson_object_set( root, "rows", rowsV ); + if( 0 != rc ) + { + cson_value_free( rowsV ); + RETURN(rc); + } + rows = cson_value_get_array(rowsV); + assert(rows); + while( SQLITE_ROW == sqlite3_step(st) ) + { + aryV = cson_sqlite3_row_to_array(st); + if( ! aryV ) RETURN(cson_rc.UnknownError); + rc = cson_array_append( rows, aryV ); + if( 0 != rc ) + { + cson_value_free( aryV ); + RETURN(rc); + } + } + *tgt = rootV; + return 0; + } +#undef RETURN +} + +int cson_sqlite3_stmt_to_json( sqlite3_stmt * st, cson_value ** tgt, char fat ) +{ + return fat + ? cson_sqlite3_stmt_to_json_fat(st,tgt) + : cson_sqlite3_stmt_to_json_slim(st,tgt) + ; +} + +int cson_sqlite3_sql_to_json( sqlite3 * db, cson_value ** tgt, char const * sql, char fat ) +{ + if( !db || !tgt || !sql || !*sql ) return cson_rc.ArgError; + else + { + sqlite3_stmt * st = NULL; + int rc = sqlite3_prepare_v2( db, sql, -1, &st, NULL ); + if( 0 != rc ) return cson_rc.IOError /* FIXME: Better error code? */; + rc = cson_sqlite3_stmt_to_json( st, tgt, fat ); + sqlite3_finalize( st ); + return rc; + } +} + +int cson_sqlite3_bind_value( sqlite3_stmt * st, int ndx, cson_value const * v ) +{ + int rc = 0; + char convertErr = 0; + if(!st) return cson_rc.ArgError; + else if( ndx < 1 ) { + rc = cson_rc.RangeError; + } + else if( cson_value_is_array(v) ){ + cson_array * ar = cson_value_get_array(v); + unsigned int len = cson_array_length_get(ar); + unsigned int i; + assert(NULL != ar); + for( i = 0; !rc && (i < len); ++i ){ + rc = cson_sqlite3_bind_value( st, (int)i+ndx, + cson_array_get(ar, i)); + } + } + else if(!v || cson_value_is_null(v)){ + rc = sqlite3_bind_null(st,ndx); + convertErr = 1; + } + else if( cson_value_is_double(v) ){ + rc = sqlite3_bind_double( st, ndx, cson_value_get_double(v) ); + convertErr = 1; + } + else if( cson_value_is_bool(v) ){ + rc = sqlite3_bind_int( st, ndx, cson_value_get_bool(v) ? 1 : 0 ); + convertErr = 1; + } + else if( cson_value_is_integer(v) ){ + rc = sqlite3_bind_int64( st, ndx, cson_value_get_integer(v) ); + convertErr = 1; + } + else if( cson_value_is_string(v) ){ + cson_string const * s = cson_value_get_string(v); + rc = sqlite3_bind_text( st, ndx, + cson_string_cstr(s), + cson_string_length_bytes(s), + SQLITE_TRANSIENT); + convertErr = 1; + } + else { + rc = cson_rc.TypeError; + } + if(convertErr && rc) switch(rc){ + case SQLITE_TOOBIG: + case SQLITE_RANGE: rc = cson_rc.RangeError; break; + case SQLITE_NOMEM: rc = cson_rc.AllocError; break; + case SQLITE_IOERR: rc = cson_rc.IOError; break; + default: rc = cson_rc.UnknownError; break; + }; + return rc; +} + + +#if defined(__cplusplus) +} /*extern "C"*/ +#endif +#undef MARKER +#endif /* CSON_ENABLE_SQLITE3 */ +/* end file ./cson_sqlite3.c */ +#endif /* FOSSIL_ENABLE_JSON */ ADDED extsrc/cson_amalgamation.h Index: extsrc/cson_amalgamation.h ================================================================== --- /dev/null +++ extsrc/cson_amalgamation.h @@ -0,0 +1,2616 @@ +#ifdef FOSSIL_ENABLE_JSON +#ifndef CSON_FOSSIL_MODE +#define CSON_FOSSIL_MODE +#endif +/* auto-generated! Do not edit! */ +/* begin file include/wh/cson/cson.h */ +#if !defined(WANDERINGHORSE_NET_CSON_H_INCLUDED) +#define WANDERINGHORSE_NET_CSON_H_INCLUDED 1 + +/*#include C99: fixed-size int types. */ +#include /* FILE decl */ + +/** @page page_cson cson JSON API + +cson (pronounced "season") is an object-oriented C API for generating +and consuming JSON (http://www.json.org) data. + +Its main claim to fame is that it can parse JSON from, and output it +to, damned near anywhere. The i/o routines use a callback function to +fetch/emit JSON data, allowing clients to easily plug in their own +implementations. Implementations are provided for string- and +FILE-based i/o. + +Project home page: http://fossil.wanderinghorse.net/repos/cson + +Author: Stephan Beal (http://www.wanderinghorse.net/home/stephan/) + +License: Dual Public Domain/MIT + +The full license text is at the bottom of the main header file +(cson.h). + +Examples of how to use the library are scattered throughout +the API documentation, in the test.c file in the source repo, +and in the wiki on the project's home page. + + +*/ + +#if defined(__cplusplus) +extern "C" { +#endif + +#if defined(_WIN32) || defined(_WIN64) +# define CSON_ENABLE_UNIX 0 +#else +# define CSON_ENABLE_UNIX 1 +#endif + + +/** @typedef some_long_int_type cson_int_t + +Typedef for JSON-like integer types. This is (long long) where feasible, +otherwise (long). +*/ +#ifdef _WIN32 +typedef __int64 cson_int_t; +#define CSON_INT_T_SFMT "I64d" +#define CSON_INT_T_PFMT "I64d" +#elif (__STDC_VERSION__ >= 199901L) || (HAVE_LONG_LONG == 1) +typedef long long cson_int_t; +#define CSON_INT_T_SFMT "lld" +#define CSON_INT_T_PFMT "lld" +#else +typedef long cson_int_t; +#define CSON_INT_T_SFMT "ld" +#define CSON_INT_T_PFMT "ld" +#endif + +/** @typedef double_or_long_double cson_double_t + + This is the type of double value used by the library. + It is only lightly tested with long double, and when using + long double the memory requirements for such values goes + up. + + Note that by default cson uses C-API defaults for numeric + precision. To use a custom precision throughout the library, one + needs to define the macros CSON_DOUBLE_T_SFMT and/or + CSON_DOUBLE_T_PFMT macros to include their desired precision, and + must build BOTH cson AND the client using these same values. For + example: + + @code + #define CSON_DOUBLE_T_PFMT ".8Lf" // for Modified Julian Day values + #define HAVE_LONG_DOUBLE + @endcode + + (Only CSON_DOUBLE_T_PFTM should be needed for most + purposes.) +*/ + +#if defined(HAVE_LONG_DOUBLE) + typedef long double cson_double_t; +# ifndef CSON_DOUBLE_T_SFMT +# define CSON_DOUBLE_T_SFMT "Lf" +# endif +# ifndef CSON_DOUBLE_T_PFMT +# define CSON_DOUBLE_T_PFMT "Lf" +# endif +#else + typedef double cson_double_t; +# ifndef CSON_DOUBLE_T_SFMT +# define CSON_DOUBLE_T_SFMT "f" +# endif +# ifndef CSON_DOUBLE_T_PFMT +# define CSON_DOUBLE_T_PFMT "f" +# endif +#endif + +/** @def CSON_VOID_PTR_IS_BIG + +ONLY define this to a true value if you know that + +(sizeof(cson_int_t) <= sizeof(void*)) + +If that is the case, cson does not need to dynamically +allocate integers. However, enabling this may cause +compilation warnings in 32-bit builds even though the code +being warned about cannot ever be called. To get around such +warnings, when building on a 64-bit environment you can define +this to 1 to get "big" integer support. HOWEVER, all clients must +also use the same value for this macro. If i knew a halfway reliable +way to determine this automatically at preprocessor-time, i would +automate this. We might be able to do halfway reliably by looking +for a large INT_MAX value? +*/ +#if !defined(CSON_VOID_PTR_IS_BIG) + +/* Largely taken from http://predef.sourceforge.net/prearch.html + +See also: http://poshlib.hookatooka.com/poshlib/trac.cgi/browser/posh.h +*/ +# if defined(_WIN64) || defined(__LP64__)/*gcc*/ \ + || defined(_M_X64) || defined(__amd64__) || defined(__amd64) \ + || defined(__x86_64__) || defined(__x86_64) \ + || defined(__ia64__) || defined(__ia64) || defined(_IA64) || defined(__IA64__) \ + || defined(_M_IA64) \ + || defined(__sparc_v9__) || defined(__sparcv9) || defined(_ADDR64) \ + || defined(__64BIT__) +# define CSON_VOID_PTR_IS_BIG 1 +# else +# define CSON_VOID_PTR_IS_BIG 0 +# endif +#endif + +/** @def CSON_INT_T_SFMT + +scanf()-compatible format token for cson_int_t. +*/ + +/** @def CSON_INT_T_PFMT + +printf()-compatible format token for cson_int_t. +*/ + + +/** @def CSON_DOUBLE_T_SFMT + +scanf()-compatible format token for cson_double_t. +*/ + +/** @def CSON_DOUBLE_T_PFMT + +printf()-compatible format token for cson_double_t. +*/ + +/** + Type IDs corresponding to JavaScript/JSON types. + + These are only in the public API to allow O(1) client-side + dispatching based on cson_value types. +*/ +enum cson_type_id { + /** + The special "undefined" value constant. + + Its value must be 0 for internal reasons. + */ + CSON_TYPE_UNDEF = 0, + /** + The special "null" value constant. + */ + CSON_TYPE_NULL = 1, + /** + The bool value type. + */ + CSON_TYPE_BOOL = 2, + /** + The integer value type, represented in this library + by cson_int_t. + */ + CSON_TYPE_INTEGER = 3, + /** + The double value type, represented in this library + by cson_double_t. + */ + CSON_TYPE_DOUBLE = 4, + /** The immutable string type. This library stores strings + as immutable UTF8. + */ + CSON_TYPE_STRING = 5, + /** The "Array" type. */ + CSON_TYPE_ARRAY = 6, + /** The "Object" type. */ + CSON_TYPE_OBJECT = 7 +}; +/** + Convenience typedef. +*/ +typedef enum cson_type_id cson_type_id; + + +/** + Convenience typedef. +*/ +typedef struct cson_value cson_value; + +/** @struct cson_value + + The core value type of this API. It is opaque to clients, and + only the cson public API should be used for setting or + inspecting their values. + + This class is opaque because stack-based usage can easily cause + leaks if one does not intimately understand the underlying + internal memory management (which sometimes changes). + + It is (as of 20110323) legal to insert a given value instance into + multiple containers (they will share ownership using reference + counting) as long as those insertions do not cause cycles. However, + be very aware that such value re-use uses a reference to the + original copy, meaning that if its value is changed once, it is + changed everywhere. Also beware that multi-threaded write + operations on such references leads to undefined behaviour. + + PLEASE read the ACHTUNGEN below... + + ACHTUNG #1: + + cson_values MUST NOT form cycles (e.g. via object or array + entries). + + Not abiding th Holy Law Of No Cycles will lead to double-frees and + the like (i.e. undefined behaviour, likely crashes due to infinite + recursion or stepping on invalid (freed) pointers). + + ACHTUNG #2: + + ALL cson_values returned as non-const cson_value pointers from any + public functions in the cson API are to be treated as if they are + heap-allocated, and MUST be freed by client by doing ONE of: + + - Passing it to cson_value_free(). + + - Adding it to an Object or Array, in which case the object/array + takes over ownership. As of 20110323, a value may be inserted into + a single container multiple times, or into multiple containers, + in which case they all share ownership (via reference counting) + of the original value (meaning any changes to it are visible in + all references to it). + + Each call to cson_value_new_xxx() MUST eventually be followed up + by one of those options. + + Some cson_value_new_XXX() implementations do not actually allocate + memory, but this is an internal implementation detail. Client code + MUST NOT rely on this behaviour and MUST treat each object + returned by such a function as if it was a freshly-allocated copy + (even if their pointer addresses are the same). + + ACHTUNG #3: + + Note that ACHTUNG #2 tells us that we must always free (or transfer + ownership of) all pointers returned bycson_value_new_xxx(), but + that two calls to (e.g.) cson_value_new_bool(1) will (or might) + return the same address. The client must not rely on the + "non-allocation" policy of such special cases, and must pass each + returned value to cson_value_free(), even if two of them have the + same address. Some special values (e.g. null, true, false, integer + 0, double 0.0, and empty strings) use shared copies and in other + places reference counting is used internally to figure out when it + is safe to destroy an object. + + + @see cson_value_new_array() + @see cson_value_new_object() + @see cson_value_new_string() + @see cson_value_new_integer() + @see cson_value_new_double() + @see cson_value_new_bool() + @see cson_value_true() + @see cson_value_false() + @see cson_value_null() + @see cson_value_free() + @see cson_value_type_id() +*/ + +/** @var cson_rc + + This object defines the error codes used by cson. + + Library routines which return int values almost always return a + value from this structure. None of the members in this struct have + published values except for the OK member, which has the value 0. + All other values might be incidentally defined where clients + can see them, but the numbers might change from release to + release, so clients should only use the symbolic names. + + Client code is expected to access these values via the shared + cson_rc object, and use them as demonstrated here: + + @code + int rc = cson_some_func(...); + if( 0 == rc ) {...success...} + else if( cson_rc.ArgError == rc ) { ... some argument was wrong ... } + else if( cson_rc.AllocError == rc ) { ... allocation error ... } + ... + @endcode + + The entries named Parse_XXX are generally only returned by + cson_parse() and friends. +*/ + +/** @struct cson_rc_ + See \ref cson_rc for details. +*/ +static const struct cson_rc_ +{ + /** The generic success value. Guaranteed to be 0. */ + const int OK; + /** Signifies an error in one or more arguments (e.g. NULL where it is not allowed). */ + const int ArgError; + /** Signifies that some argument is not in a valid range. */ + const int RangeError; + /** Signifies that some argument is not of the correct logical cson type. */ + const int TypeError; + /** Signifies an input/ouput error. */ + const int IOError; + /** Signifies an out-of-memory error. */ + const int AllocError; + /** Signifies that the called code is "NYI" (Not Yet Implemented). */ + const int NYIError; + /** Signifies that an internal error was triggered. If it happens, please report this as a bug! */ + const int InternalError; + /** Signifies that the called operation is not supported in the + current environment. e.g. missing support from 3rd-party or + platform-specific code. + */ + const int UnsupportedError; + /** + Signifies that the request resource could not be found. + */ + const int NotFoundError; + /** + Signifies an unknown error, possibly because an underlying + 3rd-party API produced an error and we have no other reasonable + error code to convert it to. + */ + const int UnknownError; + /** + Signifies that the parser found an unexpected character. + */ + const int Parse_INVALID_CHAR; + /** + Signifies that the parser found an invalid keyword (possibly + an unquoted string). + */ + const int Parse_INVALID_KEYWORD; + /** + Signifies that the parser found an invalid escape sequence. + */ + const int Parse_INVALID_ESCAPE_SEQUENCE; + /** + Signifies that the parser found an invalid Unicode character + sequence. + */ + const int Parse_INVALID_UNICODE_SEQUENCE; + /** + Signifies that the parser found an invalid numeric token. + */ + const int Parse_INVALID_NUMBER; + /** + Signifies that the parser reached its maximum defined + parsing depth before finishing the input. + */ + const int Parse_NESTING_DEPTH_REACHED; + /** + Signifies that the parser found an unclosed object or array. + */ + const int Parse_UNBALANCED_COLLECTION; + /** + Signifies that the parser found an key in an unexpected place. + */ + const int Parse_EXPECTED_KEY; + /** + Signifies that the parser expected to find a colon but + found none (e.g. between keys and values in an object). + */ + const int Parse_EXPECTED_COLON; +} cson_rc = { +0/*OK*/, +1/*ArgError*/, +2/*RangeError*/, +3/*TypeError*/, +4/*IOError*/, +5/*AllocError*/, +6/*NYIError*/, +7/*InternalError*/, +8/*UnsupportedError*/, +9/*NotFoundError*/, +10/*UnknownError*/, +11/*Parse_INVALID_CHAR*/, +12/*Parse_INVALID_KEYWORD*/, +13/*Parse_INVALID_ESCAPE_SEQUENCE*/, +14/*Parse_INVALID_UNICODE_SEQUENCE*/, +15/*Parse_INVALID_NUMBER*/, +16/*Parse_NESTING_DEPTH_REACHED*/, +17/*Parse_UNBALANCED_COLLECTION*/, +18/*Parse_EXPECTED_KEY*/, +19/*Parse_EXPECTED_COLON*/ +}; + +/** + Returns the string form of the cson_rc code corresponding to rc, or + some unspecified, non-NULL string if it is an unknown code. + + The returned bytes are static and do not changing during the + lifetime of the application. +*/ +char const * cson_rc_string(int rc); + +/** @struct cson_parse_opt + Client-configurable options for the cson_parse() family of + functions. +*/ +struct cson_parse_opt +{ + /** + Maximum object/array depth to traverse. + */ + unsigned short maxDepth; + /** + Whether or not to allow C-style comments. Do not rely on this + option being available. If the underlying parser is replaced, + this option might no longer be supported. + */ + char allowComments; +}; +typedef struct cson_parse_opt cson_parse_opt; + +/** + Empty-initialized cson_parse_opt object. +*/ +#define cson_parse_opt_empty_m { 25/*maxDepth*/, 0/*allowComments*/} + + +/** + A class for holding JSON parser information. It is primarily + intended for finding the position of a parse error. +*/ +struct cson_parse_info +{ + /** + 1-based line number. + */ + unsigned int line; + /** + 0-based column number. + */ + unsigned int col; + + /** + Length, in bytes. + */ + unsigned int length; + + /** + Error code of the parse run (0 for no error). + */ + int errorCode; + + /** + The total number of object keys successfully processed by the + parser. + */ + unsigned int totalKeyCount; + + /** + The total number of object/array values successfully processed + by the parser, including the root node. + */ + unsigned int totalValueCount; +}; +typedef struct cson_parse_info cson_parse_info; + +/** + Empty-initialized cson_parse_info object. +*/ +#define cson_parse_info_empty_m {1/*line*/,\ + 0/*col*/, \ + 0/*length*/, \ + 0/*errorCode*/, \ + 0/*totalKeyCount*/, \ + 0/*totalValueCount*/ \ + } +/** + Empty-initialized cson_parse_info object. +*/ +extern const cson_parse_info cson_parse_info_empty; + +/** + Empty-initialized cson_parse_opt object. +*/ +extern const cson_parse_opt cson_parse_opt_empty; + +/** + Client-configurable options for the cson_output() family of + functions. +*/ +struct cson_output_opt +{ + /** + Specifies how to indent (or not) output. The values + are: + + (0) == no extra indentation. + + (1) == 1 TAB character for each level. + + (>1) == that number of SPACES for each level. + */ + unsigned char indentation; + + /** + Maximum object/array depth to traverse. Traversing deeply can + be indicative of cycles in the object/array tree, and this + value is used to figure out when to abort the traversal. + */ + unsigned short maxDepth; + + /** + If true, a newline will be added to generated output, + else not. + */ + char addNewline; + + /** + If true, a space will be added after the colon operator + in objects' key/value pairs. + */ + char addSpaceAfterColon; + + /** + If set to 1 then objects/arrays containing only a single value + will not indent an extra level for that value (but will indent + on subsequent levels if that value contains multiple values). + */ + char indentSingleMemberValues; + + /** + The JSON format allows, but does not require, JSON generators + to backslash-escape forward slashes. This option enables/disables + that feature. According to JSON's inventor, Douglas Crockford: + + + It is allowed, not required. It is allowed so that JSON can be + safely embedded in HTML, which can freak out when seeing + strings containing " + + (from an email on 2011-04-08) + + The default value is 0 (because it's just damned ugly). + */ + char escapeForwardSlashes; +}; +typedef struct cson_output_opt cson_output_opt; + +/** + Empty-initialized cson_output_opt object. +*/ +#define cson_output_opt_empty_m { 0/*indentation*/,\ + 25/*maxDepth*/, \ + 0/*addNewline*/, \ + 0/*addSpaceAfterColon*/, \ + 0/*indentSingleMemberValues*/, \ + 0/*escapeForwardSlashes*/ \ + } + +/** + Empty-initialized cson_output_opt object. +*/ +extern const cson_output_opt cson_output_opt_empty; + +/** + Typedef for functions which act as an input source for + the cson JSON parser. + + The arguments are: + + - state: implementation-specific state needed by the function. + + - n: when called, *n will be the number of bytes the function + should read and copy to dest. The function MUST NOT copy more than + *n bytes to dest. Before returning, *n must be set to the number of + bytes actually copied to dest. If that number is smaller than the + original *n value, the input is assumed to be completed (thus this + is not useful with non-blocking readers). + + - dest: the destination memory to copy the data do. + + Must return 0 on success, non-0 on error (preferably a value from + cson_rc). + + The parser allows this routine to return a partial character from a + UTF multi-byte character. The input routine does not need to + concern itself with character boundaries. +*/ +typedef int (*cson_data_source_f)( void * state, void * dest, unsigned int * n ); + +/** + Typedef for functions which act as an output destination for + generated JSON. + + The arguments are: + + - state: implementation-specific state needed by the function. + + - n: the length, in bytes, of src. + + - src: the source bytes which the output function should consume. + The src pointer will be invalidated shortly after this function + returns, so the implementation must copy or ignore the data, but not + hold a copy of the src pointer. + + Must return 0 on success, non-0 on error (preferably a value from + cson_rc). + + These functions are called relatively often during the JSON-output + process, and should try to be fast. +*/ +typedef int (*cson_data_dest_f)( void * state, void const * src, unsigned int n ); + +/** + Reads JSON-formatted string data (in ASCII, UTF8, or UTF16), using the + src function to fetch all input. This function fetches each input character + from the source function, which is calls like src(srcState, buffer, bufferSize), + and processes them. If anything is not JSON-kosher then this function + fails and returns one of the non-0 cson_rc codes. + + This function is only intended to read root nodes of a JSON tree, either + a single object or a single array, containing any number of child elements. + + On success, *tgt is assigned the value of the root node of the + JSON input, and the caller takes over ownership of that memory. + On error, *tgt is not modified and the caller need not do any + special cleanup, except possibly for the input source. + + + The opt argument may point to an initialized cson_parse_opt object + which contains any settings the caller wants. If it is NULL then + default settings (the values defined in cson_parse_opt_empty) are + used. + + The info argument may be NULL. If it is not NULL then the parser + populates it with information which is useful in error + reporting. Namely, it contains the line/column of parse errors. + + The srcState argument is ignored by this function but is passed on to src, + so any output-destination-specific state can be stored there and accessed + via the src callback. + + Non-parse error conditions include: + + - (!tgt) or !src: cson_rc.ArgError + - cson_rc.AllocError can happen at any time during the input phase + + Here's a complete example of using a custom input source: + + @code + // Internal type to hold state for a JSON input string. + typedef struct + { + char const * str; // start of input string + char const * pos; // current internal cursor position + char const * end; // logical EOF (one-past-the-end) + } StringSource; + + // cson_data_source_f() impl which uses StringSource. + static int cson_data_source_StringSource( void * state, void * dest, + unsigned int * n ) + { + StringSource * ss = (StringSource*) state; + unsigned int i; + unsigned char * tgt = (unsigned char *)dest; + if( ! ss || ! n || !dest ) return cson_rc.ArgError; + else if( !*n ) return cson_rc.RangeError; + for( i = 0; + (i < *n) && (ss->pos < ss->end); + ++i, ++ss->pos, ++tgt ) + { + *tgt = *ss->pos; + } + *n = i; + return 0; + } + + ... + // Now use StringSource together with cson_parse() + StringSource ss; + cson_value * root = NULL; + char const * json = "{\"k1\":123}"; + ss.str = ss.pos = json; + ss.end = json + strlen(json); + int rc = cson_parse( &root, cson_data_source_StringSource, &ss, NULL, NULL ); + @endcode + + It is recommended that clients wrap such utility code into + type-safe wrapper functions which also initialize the internal + state object and check the user-provided parameters for legality + before passing them on to cson_parse(). For examples of this, see + cson_parse_FILE() or cson_parse_string(). + + TODOs: + + - Buffer the input in larger chunks. We currently read + byte-by-byte, but i'm too tired to write/test the looping code for + the buffering. + + @see cson_parse_FILE() + @see cson_parse_string() +*/ +int cson_parse( cson_value ** tgt, cson_data_source_f src, void * srcState, + cson_parse_opt const * opt, cson_parse_info * info ); +/** + A cson_data_source_f() implementation which requires the state argument + to be a readable (FILE*) handle. +*/ +int cson_data_source_FILE( void * state, void * dest, unsigned int * n ); + +/** + Equivalent to cson_parse( tgt, cson_data_source_FILE, src, opt ). + + @see cson_parse_filename() +*/ +int cson_parse_FILE( cson_value ** tgt, FILE * src, + cson_parse_opt const * opt, cson_parse_info * info ); + +/** + Convenience wrapper around cson_parse_FILE() which opens the given filename. + + Returns cson_rc.IOError if the file cannot be opened. + + @see cson_parse_FILE() +*/ +int cson_parse_filename( cson_value ** tgt, char const * src, + cson_parse_opt const * opt, cson_parse_info * info ); + +/** + Uses an internal helper class to pass src through cson_parse(). + See that function for the return value and argument semantics. + + src must be a string containing JSON code, at least len bytes long, + and the parser will attempt to parse exactly len bytes from src. + + If len is less than 2 (the minimum length of a legal top-node JSON + object) then cson_rc.RangeError is returned. +*/ +int cson_parse_string( cson_value ** tgt, char const * src, unsigned int len, + cson_parse_opt const * opt, cson_parse_info * info ); + + + +/** + Outputs the given value as a JSON-formatted string, sending all + output to the given callback function. It is intended for top-level + objects or arrays, but can be used with any cson_value. + + If opt is NULL then default options (the values defined in + cson_output_opt_empty) are used. + + If opt->maxDepth is exceeded while traversing the value tree, + cson_rc.RangeError is returned. + + The destState parameter is ignored by this function and is passed + on to the dest function. + + Returns 0 on success. On error, any amount of output might have been + generated before the error was triggered. + + Example: + + @code + int rc = cson_output( myValue, cson_data_dest_FILE, stdout, NULL ); + // basically equivalent to: cson_output_FILE( myValue, stdout, NULL ); + // but note that cson_output_FILE() actually uses different defaults + // for the output options. + @endcode +*/ +int cson_output( cson_value const * src, cson_data_dest_f dest, void * destState, cson_output_opt const * opt ); + + +/** + A cson_data_dest_f() implementation which requires the state argument + to be a writable (FILE*) handle. +*/ +int cson_data_dest_FILE( void * state, void const * src, unsigned int n ); + +/** + Almost equivalent to cson_output( src, cson_data_dest_FILE, dest, opt ), + with one minor difference: if opt is NULL then the default options + always include the addNewline option, since that is normally desired + for FILE output. + + @see cson_output_filename() +*/ +int cson_output_FILE( cson_value const * src, FILE * dest, cson_output_opt const * opt ); +/** + Convenience wrapper around cson_output_FILE() which writes to the given filename, destroying + any existing contents. Returns cson_rc.IOError if the file cannot be opened. + + @see cson_output_FILE() +*/ +int cson_output_filename( cson_value const * src, char const * dest, cson_output_opt const * fmt ); + +/** + Returns the virtual type of v, or CSON_TYPE_UNDEF if !v. +*/ +cson_type_id cson_value_type_id( cson_value const * v ); + + +/** Returns true if v is null, v->api is NULL, or v holds the special undefined value. */ +char cson_value_is_undef( cson_value const * v ); +/** Returns true if v contains a null value. */ +char cson_value_is_null( cson_value const * v ); +/** Returns true if v contains a bool value. */ +char cson_value_is_bool( cson_value const * v ); +/** Returns true if v contains an integer value. */ +char cson_value_is_integer( cson_value const * v ); +/** Returns true if v contains a double value. */ +char cson_value_is_double( cson_value const * v ); +/** Returns true if v contains a number (double, integer) value. */ +char cson_value_is_number( cson_value const * v ); +/** Returns true if v contains a string value. */ +char cson_value_is_string( cson_value const * v ); +/** Returns true if v contains an array value. */ +char cson_value_is_array( cson_value const * v ); +/** Returns true if v contains an object value. */ +char cson_value_is_object( cson_value const * v ); + +/** @struct cson_object + + cson_object is an opaque handle to an Object value. + + They are used like: + + @code + cson_object * obj = cson_value_get_object(myValue); + ... + @endcode + + They can be created like: + + @code + cson_value * objV = cson_value_new_object(); + cson_object * obj = cson_value_get_object(objV); + // obj is owned by objV and objV must eventually be freed + // using cson_value_free() or added to a container + // object/array (which transfers ownership to that container). + @endcode + + @see cson_value_new_object() + @see cson_value_get_object() + @see cson_value_free() +*/ + +typedef struct cson_object cson_object; + +/** @struct cson_array + + cson_array is an opaque handle to an Array value. + + They are used like: + + @code + cson_array * obj = cson_value_get_array(myValue); + ... + @endcode + + They can be created like: + + @code + cson_value * arV = cson_value_new_array(); + cson_array * ar = cson_value_get_array(arV); + // ar is owned by arV and arV must eventually be freed + // using cson_value_free() or added to a container + // object/array (which transfers ownership to that container). + @endcode + + @see cson_value_new_array() + @see cson_value_get_array() + @see cson_value_free() + +*/ +typedef struct cson_array cson_array; + +/** @struct cson_string + + cson-internal string type, opaque to client code. Strings in cson + are immutable and allocated only by library internals, never + directly by client code. + + The actual string bytes are to be allocated together in the same + memory chunk as the cson_string object, which saves us 1 malloc() + and 1 pointer member in this type (because we no longer have a + direct pointer to the memory). + + Potential TODOs: + + @see cson_string_cstr() +*/ +typedef struct cson_string cson_string; + +/** + Converts the given value to a boolean, using JavaScript semantics depending + on the concrete type of val: + + undef or null: false + + boolean: same + + integer, double: 0 or 0.0 == false, else true + + object, array: true + + string: length-0 string is false, else true. + + Returns 0 on success and assigns *v (if v is not NULL) to either 0 or 1. + On error (val is NULL) then v is not modified. +*/ +int cson_value_fetch_bool( cson_value const * val, char * v ); + +/** + Similar to cson_value_fetch_bool(), but fetches an integer value. + + The conversion, if any, depends on the concrete type of val: + + NULL, null, undefined: *v is set to 0 and 0 is returned. + + string, object, array: *v is set to 0 and + cson_rc.TypeError is returned. The error may normally be safely + ignored, but it is provided for those wanted to know whether a direct + conversion was possible. + + integer: *v is set to the int value and 0 is returned. + + double: *v is set to the value truncated to int and 0 is returned. +*/ +int cson_value_fetch_integer( cson_value const * val, cson_int_t * v ); + +/** + The same conversions and return values as + cson_value_fetch_integer(), except that the roles of int/double are + swapped. +*/ +int cson_value_fetch_double( cson_value const * val, cson_double_t * v ); + +/** + If cson_value_is_string(val) then this function assigns *str to the + contents of the string. str may be NULL, in which case this function + functions like cson_value_is_string() but returns 0 on success. + + Returns 0 if val is-a string, else non-0, in which case *str is not + modified. + + The bytes are owned by the given value and may be invalidated in any of + the following ways: + + - The value is cleaned up or freed. + + - An array or object containing the value peforms a re-allocation + (it shrinks or grows). + + And thus the bytes should be consumed before any further operations + on val or any container which holds it. + + Note that this routine does not convert non-String values to their + string representations. (Adding that ability would add more + overhead to every cson_value instance.) +*/ +int cson_value_fetch_string( cson_value const * val, cson_string ** str ); + +/** + If cson_value_is_object(val) then this function assigns *obj to the underlying + object value and returns 0, otherwise non-0 is returned and *obj is not modified. + + obj may be NULL, in which case this function works like cson_value_is_object() + but with inverse return value semantics (0==success) (and it's a few + CPU cycles slower). + + The *obj pointer is owned by val, and will be invalidated when val + is cleaned up. + + Achtung: for best results, ALWAYS pass a pointer to NULL as the + second argument, e.g.: + + @code + cson_object * obj = NULL; + int rc = cson_value_fetch_object( val, &obj ); + + // Or, more simply: + obj = cson_value_get_object( val ); + @endcode + + @see cson_value_get_object() +*/ +int cson_value_fetch_object( cson_value const * val, cson_object ** obj ); + +/** + Identical to cson_value_fetch_object(), but works on array values. + + @see cson_value_get_array() +*/ +int cson_value_fetch_array( cson_value const * val, cson_array ** tgt ); + +/** + Simplified form of cson_value_fetch_bool(). Returns 0 if val + is NULL. +*/ +char cson_value_get_bool( cson_value const * val ); + +/** + Simplified form of cson_value_fetch_integer(). Returns 0 if val + is NULL. +*/ +cson_int_t cson_value_get_integer( cson_value const * val ); + +/** + Simplified form of cson_value_fetch_double(). Returns 0.0 if val + is NULL. +*/ +cson_double_t cson_value_get_double( cson_value const * val ); + +/** + Simplified form of cson_value_fetch_string(). Returns NULL if val + is-not-a string value. +*/ +cson_string * cson_value_get_string( cson_value const * val ); + +/** + Returns a pointer to the NULL-terminated string bytes of str. + The bytes are owned by string and will be invalided when it + is cleaned up. + + If str is NULL then NULL is returned. If the string has a length + of 0 then "" is returned. + + @see cson_string_length_bytes() + @see cson_value_get_string() +*/ +char const * cson_string_cstr( cson_string const * str ); + +/** + Convenience function which returns the string bytes of + the given value if it is-a string, otherwise it returns + NULL. Note that this does no conversion of non-string types + to strings. + + Equivalent to cson_string_cstr(cson_value_get_string(val)). +*/ +char const * cson_value_get_cstr( cson_value const * val ); + +/** + Equivalent to cson_string_cmp_cstr_n(lhs, cson_string_cstr(rhs), cson_string_length_bytes(rhs)). +*/ +int cson_string_cmp( cson_string const * lhs, cson_string const * rhs ); + +/** + Compares lhs to rhs using memcmp()/strcmp() semantics. Generically + speaking it returns a negative number if lhs is less-than rhs, 0 if + they are equivalent, or a positive number if lhs is greater-than + rhs. It has the following rules for equivalence: + + - The maximum number of bytes compared is the lesser of rhsLen and + the length of lhs. If the strings do not match, but compare equal + up to the just-described comparison length, the shorter string is + considered to be less-than the longer one. + + - If lhs and rhs are both NULL, or both have a length of 0 then they will + compare equal. + + - If lhs is null/length-0 but rhs is not then lhs is considered to be less-than + rhs. + + - If rhs is null/length-0 but lhs is not then rhs is considered to be less-than + rhs. + + - i have no clue if the results are exactly correct for UTF strings. + +*/ +int cson_string_cmp_cstr_n( cson_string const * lhs, char const * rhs, unsigned int rhsLen ); + +/** + Equivalent to cson_string_cmp_cstr_n( lhs, rhs, (rhs&&*rhs)?strlen(rhs):0 ). +*/ +int cson_string_cmp_cstr( cson_string const * lhs, char const * rhs ); + +/** + Returns the length, in bytes, of str, or 0 if str is NULL. This is + an O(1) operation. + + TODO: add cson_string_length_chars() (is O(N) unless we add another + member to store the char length). + + @see cson_string_cstr() +*/ +unsigned int cson_string_length_bytes( cson_string const * str ); + +/** + Returns the number of UTF8 characters in str. This value will + be at most as long as cson_string_length_bytes() for the + same string, and less if it has multi-byte characters. + + Returns 0 if str is NULL. +*/ +unsigned int cson_string_length_utf8( cson_string const * str ); + +/** + Like cson_value_get_string(), but returns a copy of the underying + string bytes, which the caller owns and must eventually free + using free(). +*/ +char * cson_value_get_string_copy( cson_value const * val ); + +/** + Simplified form of cson_value_fetch_object(). Returns NULL if val + is-not-a object value. +*/ +cson_object * cson_value_get_object( cson_value const * val ); + +/** + Simplified form of cson_value_fetch_array(). Returns NULL if val + is-not-a array value. +*/ +cson_array * cson_value_get_array( cson_value const * val ); + +/** + Const-correct form of cson_value_get_array(). +*/ +cson_array const * cson_value_get_array_c( cson_value const * val ); + +/** + If ar is-a array and is at least (pos+1) entries long then *v (if v is not NULL) + is assigned to the value at that position (which may be NULL). + + Ownership of the *v return value is unchanged by this call. (The + containing array may share ownership of the value with other + containers.) + + If pos is out of range, non-0 is returned and *v is not modified. + + If v is NULL then this function returns 0 if pos is in bounds, but does not + otherwise return a value to the caller. +*/ +int cson_array_value_fetch( cson_array const * ar, unsigned int pos, cson_value ** v ); + +/** + Simplified form of cson_array_value_fetch() which returns NULL if + ar is NULL, pos is out of bounds or if ar has no element at that + position. +*/ +cson_value * cson_array_get( cson_array const * ar, unsigned int pos ); + +/** + Ensures that ar has allocated space for at least the given + number of entries. This never shrinks the array and never + changes its logical size, but may pre-allocate space in the + array for storing new (as-yet-unassigned) values. + + Returns 0 on success, or non-zero on error: + + - If ar is NULL: cson_rc.ArgError + + - If allocation fails: cson_rc.AllocError +*/ +int cson_array_reserve( cson_array * ar, unsigned int size ); + +/** + If ar is not NULL, sets *v (if v is not NULL) to the length of the array + and returns 0. Returns cson_rc.ArgError if ar is NULL. +*/ +int cson_array_length_fetch( cson_array const * ar, unsigned int * v ); + +/** + Simplified form of cson_array_length_fetch() which returns 0 if ar + is NULL. +*/ +unsigned int cson_array_length_get( cson_array const * ar ); + +/** + Sets the given index of the given array to the given value. + + If ar already has an item at that index then it is cleaned up and + freed before inserting the new item. + + ar is expanded, if needed, to be able to hold at least (ndx+1) + items, and any new entries created by that expansion are empty + (NULL values). + + On success, 0 is returned and ownership of v is transfered to ar. + + On error ownership of v is NOT modified, and the caller may still + need to clean it up. For example, the following code will introduce + a leak if this function fails: + + @code + cson_array_append( myArray, cson_value_new_integer(42) ); + @endcode + + Because the value created by cson_value_new_integer() has no owner + and is not cleaned up. The "more correct" way to do this is: + + @code + cson_value * v = cson_value_new_integer(42); + int rc = cson_array_append( myArray, v ); + if( 0 != rc ) { + cson_value_free( v ); + ... handle error ... + } + @endcode + +*/ +int cson_array_set( cson_array * ar, unsigned int ndx, cson_value * v ); + +/** + Appends the given value to the given array, transfering ownership of + v to ar. On error, ownership of v is not modified. Ownership of ar + is never changed by this function. + + This is functionally equivalent to + cson_array_set(ar,cson_array_length_get(ar),v), but this + implementation has slightly different array-preallocation policy + (it grows more eagerly). + + Returns 0 on success, non-zero on error. Error cases include: + + - ar or v are NULL: cson_rc.ArgError + + - Array cannot be expanded to hold enough elements: cson_rc.AllocError. + + - Appending would cause a numeric overlow in the array's size: + cson_rc.RangeError. (However, you'll get an AllocError long before + that happens!) + + On error ownership of v is NOT modified, and the caller may still + need to clean it up. See cson_array_set() for the details. + +*/ +int cson_array_append( cson_array * ar, cson_value * v ); + + +/** + Creates a new cson_value from the given boolean value. + + Ownership of the new value is passed to the caller, who must + eventually either free the value using cson_value_free() or + inserting it into a container (array or object), which transfers + ownership to the container. See the cson_value class documentation + for more details. + + Semantically speaking this function Returns NULL on allocation + error, but the implementation never actually allocates for this + case. Nonetheless, it must be treated as if it were an allocated + value. +*/ +cson_value * cson_value_new_bool( char v ); + + +/** + Alias for cson_value_new_bool(v). +*/ +cson_value * cson_new_bool(char v); + +/** + Returns the special JSON "null" value. When outputing JSON, + its string representation is "null" (without the quotes). + + See cson_value_new_bool() for notes regarding the returned + value's memory. +*/ +cson_value * cson_value_null( void ); + +/** + Equivalent to cson_value_new_bool(1). +*/ +cson_value * cson_value_true( void ); + +/** + Equivalent to cson_value_new_bool(0). +*/ +cson_value * cson_value_false( void ); + +/** + Semantically the same as cson_value_new_bool(), but for integers. +*/ +cson_value * cson_value_new_integer( cson_int_t v ); + +/** + Alias for cson_value_new_integer(v). +*/ +cson_value * cson_new_int(cson_int_t v); + +/** + Semantically the same as cson_value_new_bool(), but for doubles. +*/ +cson_value * cson_value_new_double( cson_double_t v ); + +/** + Alias for cson_value_new_double(v). +*/ +cson_value * cson_new_double(cson_double_t v); + +/** + Semantically the same as cson_value_new_bool(), but for strings. + This creates a JSON value which copies the first n bytes of str. + The string will automatically be NUL-terminated. + + Note that if str is NULL or n is 0, this function still + returns non-NULL value representing that empty string. + + Returns NULL on allocation error. + + See cson_value_new_bool() for important information about the + returned memory. +*/ +cson_value * cson_value_new_string( char const * str, unsigned int n ); + +/** + Allocates a new "object" value and transfers ownership of it to the + caller. It must eventually be destroyed, by the caller or its + owning container, by passing it to cson_value_free(). + + Returns NULL on allocation error. + + Post-conditions: cson_value_is_object(value) will return true. + + @see cson_value_new_array() + @see cson_value_free() +*/ +cson_value * cson_value_new_object( void ); + +/** + This works like cson_value_new_object() but returns an Object + handle directly. + + The value handle for the returned object can be fetched with + cson_object_value(theObject). + + Ownership is transfered to the caller, who must eventually free it + by passing the Value handle (NOT the Object handle) to + cson_value_free() or passing ownership to a parent container. + + Returns NULL on error (out of memory). +*/ +cson_object * cson_new_object( void ); + +/** + Identical to cson_new_object() except that it creates + an Array. +*/ +cson_array * cson_new_array( void ); + +/** + Identical to cson_new_object() except that it creates + a String. +*/ +cson_string * cson_new_string(char const * val, unsigned int len); + +/** + Equivalent to cson_value_free(cson_object_value(x)). +*/ +void cson_free_object(cson_object *x); + +/** + Equivalent to cson_value_free(cson_array_value(x)). +*/ +void cson_free_array(cson_array *x); + +/** + Equivalent to cson_value_free(cson_string_value(x)). +*/ +void cson_free_string(cson_string *x); + + +/** + Allocates a new "array" value and transfers ownership of it to the + caller. It must eventually be destroyed, by the caller or its + owning container, by passing it to cson_value_free(). + + Returns NULL on allocation error. + + Post-conditions: cson_value_is_array(value) will return true. + + @see cson_value_new_object() + @see cson_value_free() +*/ +cson_value * cson_value_new_array( void ); + +/** + Frees any resources owned by v, then frees v. If v is a container + type (object or array) its children are also freed (recursively). + + If v is NULL, this is a no-op. + + This function decrements a reference count and only destroys the + value if its reference count drops to 0. Reference counts are + increased by either inserting the value into a container or via + cson_value_add_reference(). Even if this function does not + immediately destroy the value, the value must be considered, from + the perspective of that client code, to have been + destroyed/invalidated by this call. + + + @see cson_value_new_object() + @see cson_value_new_array() + @see cson_value_add_reference() +*/ +void cson_value_free(cson_value * v); + +/** + Alias for cson_value_free(). +*/ +void cson_free_value(cson_value * v); + + +/** + Functionally similar to cson_array_set(), but uses a string key + as an index. Like arrays, if a value already exists for the given key, + it is destroyed by this function before inserting the new value. + + If v is NULL then this call is equivalent to + cson_object_unset(obj,key). Note that (v==NULL) is treated + differently from v having the special null value. In the latter + case, the key is set to the special null value. + + The key may be encoded as ASCII or UTF8. Results are undefined + with other encodings, and the errors won't show up here, but may + show up later, e.g. during output. + + Returns 0 on success, non-0 on error. It has the following error + cases: + + - cson_rc.ArgError: obj or key are NULL or strlen(key) is 0. + + - cson_rc.AllocError: an out-of-memory error + + On error ownership of v is NOT modified, and the caller may still + need to clean it up. For example, the following code will introduce + a leak if this function fails: + + @code + cson_object_set( myObj, "foo", cson_value_new_integer(42) ); + @endcode + + Because the value created by cson_value_new_integer() has no owner + and is not cleaned up. The "more correct" way to do this is: + + @code + cson_value * v = cson_value_new_integer(42); + int rc = cson_object_set( myObj, "foo", v ); + if( 0 != rc ) { + cson_value_free( v ); + ... handle error ... + } + @endcode + + Potential TODOs: + + - Add an overload which takes a cson_value key instead. To get + any value out of that we first need to be able to convert arbitrary + value types to strings. We could simply to-JSON them and use those + as keys. +*/ +int cson_object_set( cson_object * obj, char const * key, cson_value * v ); + +/** + Functionaly equivalent to cson_object_set(), but takes a + cson_string() as its KEY type. The string will be reference-counted + like any other values, and the key may legally be used within this + same container (as a value) or others (as a key or value) at the + same time. + + Returns 0 on success. On error, ownership (i.e. refcounts) of key + and value are not modified. On success key and value will get + increased refcounts unless they are replacing themselves (which is + a harmless no-op). +*/ +int cson_object_set_s( cson_object * obj, cson_string * key, cson_value * v ); + +/** + Removes a property from an object. + + If obj contains the given key, it is removed and 0 is returned. If + it is not found, cson_rc.NotFoundError is returned (which can + normally be ignored by client code). + + cson_rc.ArgError is returned if obj or key are NULL or key has + a length of 0. + + Returns 0 if the given key is found and removed. + + This is functionally equivalent calling + cson_object_set(obj,key,NULL). +*/ +int cson_object_unset( cson_object * obj, char const * key ); + +/** + Searches the given object for a property with the given key. If found, + it is returned. If no match is found, or any arguments are NULL, NULL is + returned. The returned object is owned by obj, and may be invalidated + by ANY operations which change obj's property list (i.e. add or remove + properties). + + FIXME: allocate the key/value pairs like we do for cson_array, + to get improve the lifetimes of fetched values. + + @see cson_object_fetch_sub() + @see cson_object_get_sub() +*/ +cson_value * cson_object_get( cson_object const * obj, char const * key ); + +/** + Equivalent to cson_object_get() but takes a cson_string argument + instead of a C-style string. +*/ +cson_value * cson_object_get_s( cson_object const * obj, cson_string const *key ); + +/** + Similar to cson_object_get(), but removes the value from the parent + object's ownership. If no item is found then NULL is returned, else + the object (now owned by the caller or possibly shared with other + containers) is returned. + + Returns NULL if either obj or key are NULL or key has a length + of 0. + + This function reduces the returned value's reference count but has + the specific property that it does not treat refcounts 0 and 1 + identically, meaning that the returned object may have a refcount + of 0. This behaviour works around a corner-case where we want to + extract a child element from its parent and then destroy the parent + (which leaves us in an undesireable (normally) reference count + state). +*/ +cson_value * cson_object_take( cson_object * obj, char const * key ); + +/** + Fetches a property from a child (or [great-]*grand-child) object. + + obj is the object to search. + + path is a delimited string, where the delimiter is the given + separator character. + + This function searches for the given path, starting at the given object + and traversing its properties as the path specifies. If a given part of the + path is not found, then this function fails with cson_rc.NotFoundError. + + If it finds the given path, it returns the value by assiging *tgt + to it. If tgt is NULL then this function has no side-effects but + will return 0 if the given path is found within the object, so it can be used + to test for existence without fetching it. + + Returns 0 if it finds an entry, cson_rc.NotFoundError if it finds + no item, and any other non-zero error code on a "real" error. Errors include: + + - obj or path are NULL: cson_rc.ArgError + + - separator is 0, or path is an empty string or contains only + separator characters: cson_rc.RangeError + + - There is an upper limit on how long a single path component may + be (some "reasonable" internal size), and cson_rc.RangeError is + returned if that length is violated. + + + Limitations: + + - It has no way to fetch data from arrays this way. i could + imagine, e.g., a path of "subobj.subArray.0" for + subobj.subArray[0], or "0.3.1" for [0][3][1]. But i'm too + lazy/tired to add this. + + Example usage: + + + Assume we have a JSON structure which abstractly looks like: + + @code + {"subobj":{"subsubobj":{"myValue":[1,2,3]}}} + @endcode + + Out goal is to get the value of myValue. We can do that with: + + @code + cson_value * v = NULL; + int rc = cson_object_fetch_sub( object, &v, "subobj.subsubobj.myValue", '.' ); + @endcode + + Note that because keys in JSON may legally contain a '.', the + separator must be specified by the caller. e.g. the path + "subobj/subsubobj/myValue" with separator='/' is equivalent the + path "subobj.subsubobj.myValue" with separator='.'. The value of 0 + is not legal as a separator character because we cannot + distinguish that use from the real end-of-string without requiring + the caller to also pass in the length of the string. + + Multiple successive separators in the list are collapsed into a + single separator for parsing purposes. e.g. the path "a...b...c" + (separator='.') is equivalent to "a.b.c". + + @see cson_object_get_sub() + @see cson_object_get_sub2() +*/ +int cson_object_fetch_sub( cson_object const * obj, cson_value ** tgt, char const * path, char separator ); + +/** + Similar to cson_object_fetch_sub(), but derives the path separator + character from the first byte of the path argument. e.g. the + following arg equivalent: + + @code + cson_object_fetch_sub( obj, &tgt, "foo.bar.baz", '.' ); + cson_object_fetch_sub2( obj, &tgt, ".foo.bar.baz" ); + @endcode +*/ +int cson_object_fetch_sub2( cson_object const * obj, cson_value ** tgt, char const * path ); + +/** + Convenience form of cson_object_fetch_sub() which returns NULL if the given + item is not found. +*/ +cson_value * cson_object_get_sub( cson_object const * obj, char const * path, char sep ); + +/** + Convenience form of cson_object_fetch_sub2() which returns NULL if the given + item is not found. +*/ +cson_value * cson_object_get_sub2( cson_object const * obj, char const * path ); + +/** @enum CSON_MERGE_FLAGS + + Flags for cson_object_merge(). +*/ +enum CSON_MERGE_FLAGS { + CSON_MERGE_DEFAULT = 0, + CSON_MERGE_REPLACE = 0x01, + CSON_MERGE_NO_RECURSE = 0x02 +}; + +/** + "Merges" the src object's properties into dest. Each property in + src is copied (using reference counting, not cloning) into dest. If + dest already has the given property then behaviour depends on the + flags argument: + + If flag has the CSON_MERGE_REPLACE bit set then this function will + by default replace non-object properties with the src property. If + src and dest both have the property AND it is an Object then this + function operates recursively on those objects. If + CSON_MERGE_NO_RECURSE is set then objects are not recursed in this + manner, and will be completely replaced if CSON_MERGE_REPLACE is + set. + + Array properties in dest are NOT recursed for merging - they are + either replaced or left as-is, depending on whether flags contains + he CSON_MERGE_REPLACE bit. + + Returns 0 on success. The error conditions are: + + - dest or src are NULL or (dest==src) returns cson_rc.ArgError. + + - dest or src contain cyclic references - this will likely cause a + crash due to endless recursion. + + Potential TODOs: + + - Add a flag to copy clones, not the original values. +*/ +int cson_object_merge( cson_object * dest, cson_object const * src, int flags ); + + +/** + An iterator type for traversing object properties. + + Its values must be considered private, not to be touched by client + code. + + @see cson_object_iter_init() + @see cson_object_iter_next() +*/ +struct cson_object_iterator +{ + + /** @internal + The underlying object. + */ + cson_object const * obj; + /** @internal + Current position in the property list. + */ + unsigned int pos; +}; +typedef struct cson_object_iterator cson_object_iterator; + +/** + Empty-initialized cson_object_iterator object. +*/ +#define cson_object_iterator_empty_m {NULL/*obj*/,0/*pos*/} + +/** + Empty-initialized cson_object_iterator object. +*/ +extern const cson_object_iterator cson_object_iterator_empty; + +/** + Initializes the given iterator to point at the start of obj's + properties. Returns 0 on success or cson_rc.ArgError if !obj + or !iter. + + obj must outlive iter, or results are undefined. Results are also + undefined if obj is modified while the iterator is active. + + @see cson_object_iter_next() +*/ +int cson_object_iter_init( cson_object const * obj, cson_object_iterator * iter ); + +/** @struct cson_kvp + +This class represents a key/value pair and is used for storing +object properties. It is opaque to client code, and the public +API only uses this type for purposes of iterating over cson_object +properties using the cson_object_iterator interfaces. +*/ + +typedef struct cson_kvp cson_kvp; + +/** + Returns the next property from the given iterator's object, or NULL + if the end of the property list as been reached. + + Note that the order of object properties is undefined by the API, + and may change from version to version. + + The returned memory belongs to the underlying object and may be + invalidated by any changes to that object. + + Example usage: + + @code + cson_object_iterator it; + cson_object_iter_init( myObject, &it ); // only fails if either arg is 0 + cson_kvp * kvp; + cson_string const * key; + cson_value const * val; + while( (kvp = cson_object_iter_next(&it) ) ) + { + key = cson_kvp_key(kvp); + val = cson_kvp_value(kvp); + ... + } + @endcode + + There is no need to clean up an iterator, as it holds no dynamic resources. + + @see cson_kvp_key() + @see cson_kvp_value() +*/ +cson_kvp * cson_object_iter_next( cson_object_iterator * iter ); + + +/** + Returns the key associated with the given key/value pair, + or NULL if !kvp. The memory is owned by the object which contains + the key/value pair, and may be invalidated by any modifications + to that object. +*/ +cson_string * cson_kvp_key( cson_kvp const * kvp ); + +/** + Returns the value associated with the given key/value pair, + or NULL if !kvp. The memory is owned by the object which contains + the key/value pair, and may be invalidated by any modifications + to that object. +*/ +cson_value * cson_kvp_value( cson_kvp const * kvp ); + +/** @typedef some unsigned int type cson_size_t + +*/ +typedef unsigned int cson_size_t; + +/** + A generic buffer class. + + They can be used like this: + + @code + cson_buffer b = cson_buffer_empty; + int rc = cson_buffer_reserve( &buf, 100 ); + if( 0 != rc ) { ... allocation error ... } + ... use buf.mem ... + ... then free it up ... + cson_buffer_reserve( &buf, 0 ); + @endcode + + To take over ownership of a buffer's memory: + + @code + void * mem = b.mem; + // mem is b.capacity bytes long, but only b.used + // bytes of it has been "used" by the API. + b = cson_buffer_empty; + @endcode + + The memory now belongs to the caller and must eventually be + free()d. +*/ +struct cson_buffer +{ + /** + The number of bytes allocated for this object. + Use cson_buffer_reserve() to change its value. + */ + cson_size_t capacity; + /** + The number of bytes "used" by this object. It is not needed for + all use cases, and management of this value (if needed) is up + to the client. The cson_buffer public API does not use this + member. The intention is that this can be used to track the + length of strings which are allocated via cson_buffer, since + they need an explicit length and/or null terminator. + */ + cson_size_t used; + + /** + This is a debugging/metric-counting value + intended to help certain malloc()-conscious + clients tweak their memory reservation sizes. + Each time cson_buffer_reserve() expands the + buffer, it increments this value by 1. + */ + cson_size_t timesExpanded; + + /** + The memory allocated for and owned by this buffer. + Use cson_buffer_reserve() to change its size or + free it. To take over ownership, do: + + @code + void * myptr = buf.mem; + buf = cson_buffer_empty; + @endcode + + (You might also need to store buf.used and buf.capacity, + depending on what you want to do with the memory.) + + When doing so, the memory must eventually be passed to free() + to deallocate it. + */ + unsigned char * mem; +}; +/** Convenience typedef. */ +typedef struct cson_buffer cson_buffer; + +/** An empty-initialized cson_buffer object. */ +#define cson_buffer_empty_m {0/*capacity*/,0/*used*/,0/*timesExpanded*/,NULL/*mem*/} +/** An empty-initialized cson_buffer object. */ +extern const cson_buffer cson_buffer_empty; + +/** + Uses cson_output() to append all JSON output to the given buffer + object. The semantics for the (v, opt) parameters, and the return + value, are as documented for cson_output(). buf must be a non-NULL + pointer to a properly initialized buffer (see example below). + + Ownership of buf is not changed by calling this. + + On success 0 is returned and the contents of buf.mem are guaranteed + to be NULL-terminated. On error the buffer might contain partial + contents, and it should not be used except to free its contents. + + On error non-zero is returned. Errors include: + + - Invalid arguments: cson_rc.ArgError + + - Buffer cannot be expanded (runs out of memory): cson_rc.AllocError + + Example usage: + + @code + cson_buffer buf = cson_buffer_empty; + // optional: cson_buffer_reserve(&buf, 1024 * 10); + int rc = cson_output_buffer( myValue, &buf, NULL ); + if( 0 != rc ) { + ... error! ... + } + else { + ... use buffer ... + puts((char const*)buf.mem); + } + // In both cases, we eventually need to clean up the buffer: + cson_buffer_reserve( &buf, 0 ); + // Or take over ownership of its memory: + { + char * mem = (char *)buf.mem; + buf = cson_buffer_empty; + ... + free(mem); + } + @endcode + + @see cson_output() + +*/ +int cson_output_buffer( cson_value const * v, cson_buffer * buf, + cson_output_opt const * opt ); + +/** + This works identically to cson_parse_string(), but takes a + cson_buffer object as its input. buf->used bytes of buf->mem are + assumed to be valid JSON input, but it need not be NUL-terminated + (we only read up to buf->used bytes). The value of buf->used is + assumed to be the "string length" of buf->mem, i.e. not including + the NUL terminator. + + Returns 0 on success, non-0 on error. + + See cson_parse() for the semantics of the tgt, opt, and err + parameters. +*/ +int cson_parse_buffer( cson_value ** tgt, cson_buffer const * buf, + cson_parse_opt const * opt, cson_parse_info * err ); + + +/** + Reserves the given amount of memory for the given buffer object. + + If n is 0 then buf->mem is freed and its state is set to + NULL/0 values. + + If buf->capacity is less than or equal to n then 0 is returned and + buf is not modified. + + If n is larger than buf->capacity then buf->mem is (re)allocated + and buf->capacity contains the new length. Newly-allocated bytes + are filled with zeroes. + + On success 0 is returned. On error non-0 is returned and buf is not + modified. + + buf->mem is owned by buf and must eventually be freed by passing an + n value of 0 to this function. + + buf->used is never modified by this function unless n is 0, in which case + it is reset. +*/ +int cson_buffer_reserve( cson_buffer * buf, cson_size_t n ); + +/** + Fills all bytes of the given buffer with the given character. + Returns the number of bytes set (buf->capacity), or 0 if + !buf or buf has no memory allocated to it. +*/ +cson_size_t cson_buffer_fill( cson_buffer * buf, char c ); + +/** + Uses a cson_data_source_f() function to buffer input into a + cson_buffer. + + dest must be a non-NULL, initialized (though possibly empty) + cson_buffer object. Its contents, if any, will be overwritten by + this function, and any memory it holds might be re-used. + + The src function is called, and passed the state parameter, to + fetch the input. If it returns non-0, this function returns that + error code. src() is called, possibly repeatedly, until it reports + that there is no more data. + + Whether or not this function succeeds, dest still owns any memory + pointed to by dest->mem, and the client must eventually free it by + calling cson_buffer_reserve(dest,0). + + dest->mem might (and possibly will) be (re)allocated by this + function, so any pointers to it held from before this call might be + invalidated by this call. + + On error non-0 is returned and dest has almost certainly been + modified but its state must be considered incomplete. + + Errors include: + + - dest or src are NULL (cson_rc.ArgError) + + - Allocation error (cson_rc.AllocError) + + - src() returns an error code + + Whether or not the state parameter may be NULL depends on + the src implementation requirements. + + On success dest will contain the contents read from the input + source. dest->used will be the length of the read-in data, and + dest->mem will point to the memory. dest->mem is automatically + NUL-terminated if this function succeeds, but dest->used does not + count that terminator. On error the state of dest->mem must be + considered incomplete, and is not guaranteed to be NUL-terminated. + + Example usage: + + @code + cson_buffer buf = cson_buffer_empty; + int rc = cson_buffer_fill_from( &buf, + cson_data_source_FILE, + stdin ); + if( rc ) + { + fprintf(stderr,"Error %d (%s) while filling buffer.\n", + rc, cson_rc_string(rc)); + cson_buffer_reserve( &buf, 0 ); + return ...; + } + ... use the buf->mem ... + ... clean up the buffer ... + cson_buffer_reserve( &buf, 0 ); + @endcode + + To take over ownership of the buffer's memory, do: + + @code + void * mem = buf.mem; + buf = cson_buffer_empty; + @endcode + + In which case the memory must eventually be passed to free() to + free it. +*/ +int cson_buffer_fill_from( cson_buffer * dest, cson_data_source_f src, void * state ); + + +/** + Increments the reference count for the given value. This is a + low-level operation and should not normally be used by client code + without understanding exactly what side-effects it introduces. + Mis-use can lead to premature destruction or cause a value instance + to never be properly destructed (i.e. a memory leak). + + This function is probably only useful for the following cases: + + - You want to hold a reference to a value which is itself contained + in one or more containers, and you need to be sure that your + reference outlives the container(s) and/or that you can free your + copy of the reference without invaliding any references to the same + value held in containers. + + - You want to implement "value sharing" behaviour without using an + object or array to contain the shared value. This can be used to + ensure the lifetime of the shared value instance. Each sharing + point adds a reference and simply passed the value to + cson_value_free() when they're done. The object will be kept alive + for other sharing points which added a reference. + + Normally any such value handles would be invalidated when the + parent container(s) is/are cleaned up, but this function can be + used to effectively delay the cleanup. + + This function, at its lowest level, increments the value's + reference count by 1. + + To decrement the reference count, pass the value to + cson_value_free(), after which the value must be considered, from + the perspective of that client code, to be destroyed (though it + will not be if there are still other live references to + it). cson_value_free() will not _actually_ destroy the value until + its reference count drops to 0. + + Returns 0 on success. The only error conditions are if v is NULL + (cson_rc.ArgError) or if the reference increment would overflow + (cson_rc.RangeError). In theory a client would get allocation + errors long before the reference count could overflow (assuming + those reference counts come from container insertions, as opposed + to via this function). + + Insider notes which clients really need to know: + + For shared/constant value instances, such as those returned by + cson_value_true() and cson_value_null(), this function has no side + effects - it does not actually modify the reference count because + (A) those instances are shared across all client code and (B) those + objects are static and never get cleaned up. However, that is an + implementation detail which client code should not rely on. In + other words, if you call cson_value_add_reference() 3 times using + the value returned by cson_value_true() (which is incidentally a + shared cson_value instance), you must eventually call + cson_value_free() 3 times to (semantically) remove those + references. However, internally the reference count for that + specific cson_value instance will not be modified and those + objects will never be freed (they're stack-allocated). + + It might be interesting to note that newly-created objects + have a reference count of 0 instead of 1. This is partly because + if the initial reference is counted then it makes ownership + problematic when inserting values into containers. e.g. consider the + following code: + + @code + // ACHTUNG: this code is hypothetical and does not reflect + // what actually happens! + cson_value * v = + cson_value_new_integer( 42 ); // v's refcount = 1 + cson_array_append( myArray, v ); // v's refcount = 2 + @endcode + + If that were the case, the client would be forced to free his own + reference after inserting it into the container (which is a bit + counter-intuitive as well as intrusive). It would look a bit like + the following and would have to be done after every create/insert + operation: + + @code + // ACHTUNG: this code is hypothetical and does not reflect + // what actually happens! + cson_array_append( myArray, v ); // v's refcount = 2 + cson_value_free( v ); // v's refcount = 1 + @endcode + + (As i said: it's counter-intuitive and intrusive.) + + Instead, values start with a refcount of 0 and it is only increased + when the value is added to an object/array container or when this + function is used to manually increment it. cson_value_free() treats + a refcount of 0 or 1 equivalently, destroying the value + instance. The only semantic difference between 0 and 1, for + purposes of cleaning up, is that a value with a non-0 refcount has + been had its refcount adjusted, whereas a 0 refcount indicates a + fresh, "unowned" reference. +*/ +int cson_value_add_reference( cson_value * v ); + +#if 0 +/** + DO NOT use this unless you know EXACTLY what you're doing. + It is only in the public API to work around a couple corner + cases involving extracting child elements and discarding + their parents. + + This function sets v's reference count to the given value. + It does not clean up the object if rc is 0. + + Returns 0 on success, non-0 on error. +*/ +int cson_value_refcount_set( cson_value * v, unsigned short rc ); +#endif + +/** + Deeply copies a JSON value, be it an object/array or a "plain" + value (e.g. number/string/boolean). If cv is not NULL then this + function makes a deep clone of it and returns that clone. Ownership + of the clone is identical t transfered to the caller, who must + eventually free the value using cson_value_free() or add it to a + container object/array to transfer ownership to the container. The + returned object will be of the same logical type as orig. + + ACHTUNG: if orig contains any cyclic references at any depth level + this function will endlessly recurse. (Having _any_ cyclic + references violates this library's requirements.) + + Returns NULL if orig is NULL or if cloning fails. Assuming that + orig is in a valid state, the only "likely" error case is that an + allocation fails while constructing the clone. In other words, if + cloning fails due to something other than an allocation error then + either orig is in an invalid state or there is a bug. + + When this function clones Objects or Arrays it shares any immutable + values (including object keys) between the parent and the + clone. Mutable values (Objects and Arrays) are copied, however. + For example, if we clone: + + @code + { a: 1, b: 2, c:["hi"] } + @endcode + + The cloned object and the array "c" would be a new Object/Array + instances but the object keys (a,b,b) and the values of (a,b), as + well as the string value within the "c" array, would be shared + between the original and the clone. The "c" array itself would be + deeply cloned, such that future changes to the clone are not + visible to the parent, and vice versa, but immutable values within + the array are shared (in this case the string "hi"). The + justification for this heuristic is that immutable values can never + be changed, so there is no harm in sharing them across + clones. Additionally, such types can never contribute to cycles in + a JSON tree, so they are safe to share this way. Objects and + Arrays, on the other hand, can be modified later and can contribute + to cycles, and thus the clone needs to be an independent instance. + Note, however, that if this function directly passed a + non-Object/Array, that value is deeply cloned. The sharing + behaviour only applies when traversing Objects/Arrays. +*/ +cson_value * cson_value_clone( cson_value const * orig ); + +/** + Returns the value handle associated with s. The handle itself owns + s, and ownership of the handle is not changed by calling this + function. If the returned handle is part of a container, calling + cson_value_free() on the returned handle invoked undefined + behaviour (quite possibly downstream when the container tries to + use it). + + This function only returns NULL if s is NULL. The length of the + returned string is cson_string_length_bytes(). +*/ +cson_value * cson_string_value(cson_string const * s); +/** + The Object form of cson_string_value(). See that function + for full details. +*/ +cson_value * cson_object_value(cson_object const * s); + +/** + The Array form of cson_string_value(). See that function + for full details. +*/ +cson_value * cson_array_value(cson_array const * s); + + +/** + Calculates the approximate in-memory-allocated size of v, + recursively if it is a container type, with the following caveats + and limitations: + + If a given value is reference counted then it is only and multiple + times within a traversed container, each reference is counted at + full cost. We have no way of knowing if a given reference has been + visited already and whether it should or should not be counted, so + we pessimistically count them even though the _might_ not really + count for the given object tree (it depends on where the other open + references live). + + This function returns 0 if any of the following are true: + + - v is NULL + + - v is one of the special singleton values (null, bools, empty + string, int 0, double 0.0) + + All other values require an allocation, and this will return their + total memory cost, including the cson-specific internals and the + native value(s). + + Note that because arrays and objects might have more internal slots + allocated than used, the alloced size of a container does not + necessarily increase when a new item is inserted into it. An interesting + side-effect of this is that when cson_clone()ing an array or object, the + size of the clone can actually be less than the original. +*/ +unsigned int cson_value_msize(cson_value const * v); + +/** + Parses command-line-style arguments into a JSON object. + + It expects arguments to be in any of these forms, and any number + of leading dashes are treated identically: + + --key : Treats key as a boolean with a true value. + + --key=VAL : Treats VAL as either a double, integer, or string. + + --key= : Treats key as a JSON null (not literal NULL) value. + + Arguments not starting with a dash are skipped. + + Each key/value pair is inserted into an object. If a given key + appears more than once then only the final entry is actually + stored. + + argc and argv are expected to be values from main() (or similar, + possibly adjusted to remove argv[0]). + + tgt must be either a pointer to NULL or a pointer to a + client-provided Object. If (NULL==*tgt) then this function + allocates a new object and on success it stores the new object in + *tgt (it is owned by the caller). If (NULL!=*tgt) then it is + assumed to be a properly allocated object. DO NOT pass a pointer to + an unitialized pointer, as that will fool this function into + thinking it is a valid object and Undefined Behaviour will ensue. + + If count is not NULL then the number of arugments parsed by this + function are assigned to it. On error, count will be the number of + options successfully parsed before the error was encountered. + + On success: + + - 0 is returned. + + - If (*tgt==NULL) then *tgt is assigned to a newly-allocated + object, owned by the caller. Note that even if no arguments are + parsed, the object is still created. + + On error: + + - non-0 is returned + + - If (*tgt==NULL) then it is not modified. + + - If (*tgt!=NULL) (i.e., the caller provides his own object) then + it might contain partial results. +*/ +int cson_parse_argv_flags( int argc, char const * const * argv, + cson_object ** tgt, unsigned int * count ); + + +/* LICENSE + +This software's source code, including accompanying documentation and +demonstration applications, are licensed under the following +conditions... + +Certain files are imported from external projects and have their own +licensing terms. Namely, the JSON_parser.* files. See their files for +their official licenses, but the summary is "do what you want [with +them] but leave the license text and copyright in place." + +The author (Stephan G. Beal [http://wanderinghorse.net/home/stephan/]) +explicitly disclaims copyright in all jurisdictions which recognize +such a disclaimer. In such jurisdictions, this software is released +into the Public Domain. + +In jurisdictions which do not recognize Public Domain property +(e.g. Germany as of 2011), this software is Copyright (c) 2011 by +Stephan G. Beal, and is released under the terms of the MIT License +(see below). + +In jurisdictions which recognize Public Domain property, the user of +this software may choose to accept it either as 1) Public Domain, 2) +under the conditions of the MIT License (see below), or 3) under the +terms of dual Public Domain/MIT License conditions described here, as +they choose. + +The MIT License is about as close to Public Domain as a license can +get, and is described in clear, concise terms at: + + http://en.wikipedia.org/wiki/MIT_License + +The full text of the MIT License follows: + +-- +Copyright (c) 2011 Stephan G. Beal (http://wanderinghorse.net/home/stephan/) + +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. + +--END OF MIT LICENSE-- + +For purposes of the above license, the term "Software" includes +documentation and demonstration source code which accompanies +this software. ("Accompanies" = is contained in the Software's +primary public source code repository.) + +*/ + +#if defined(__cplusplus) +} /*extern "C"*/ +#endif + +#endif /* WANDERINGHORSE_NET_CSON_H_INCLUDED */ +/* end file include/wh/cson/cson.h */ +/* begin file include/wh/cson/cson_sqlite3.h */ +/** @file cson_sqlite3.h + +This file contains cson's public sqlite3-to-JSON API declarations +and API documentation. If CSON_ENABLE_SQLITE3 is not defined, +or is defined to 0, then including this file will have no side-effects +other than defining CSON_ENABLE_SQLITE3 (if it was not defined) to 0 +and defining a few include guard macros. i.e. if CSON_ENABLE_SQLITE3 +is not set to a true value then the API is not visible. + +This API requires that be in the INCLUDES path and that +the client eventually link to (or directly embed) the sqlite3 library. +*/ +#if !defined(WANDERINGHORSE_NET_CSON_SQLITE3_H_INCLUDED) +#define WANDERINGHORSE_NET_CSON_SQLITE3_H_INCLUDED 1 +#if !defined(CSON_ENABLE_SQLITE3) +# if defined(DOXYGEN) +#define CSON_ENABLE_SQLITE3 1 +# else +#define CSON_ENABLE_SQLITE3 1 +# endif +#endif + +#if CSON_ENABLE_SQLITE3 /* we do this here for the sake of the amalgamation build */ +#include + +#if defined(__cplusplus) +extern "C" { +#endif + +/** + Converts a single value from a single 0-based column index to its JSON + equivalent. + + On success it returns a new JSON value, which will have a different concrete + type depending on the field type reported by sqlite3_column_type(st,col): + + Integer, double, null, or string (TEXT and BLOB data, though not + all blob data is legal for a JSON string). + + st must be a sqlite3_step()'d row and col must be a 0-based column + index within that result row. + */ +cson_value * cson_sqlite3_column_to_value( sqlite3_stmt * st, int col ); + +/** + Creates a JSON Array object containing the names of all columns + of the given prepared statement handle. + + Returns a new array value on success, which the caller owns. Its elements + are in the same order as in the underlying query. + + On error NULL is returned. + + st is not traversed or freed by this function - only the column + count and names are read. +*/ +cson_value * cson_sqlite3_column_names( sqlite3_stmt * st ); + +/** + Creates a JSON Object containing key/value pairs corresponding + to the result columns in the current row of the given statement + handle. st must be a sqlite3_step()'d row result. + + On success a new Object is returned which is owned by the + caller. On error NULL is returned. + + cson_sqlite3_column_to_value() is used to convert each column to a + JSON value, and the column names are taken from + sqlite3_column_name(). +*/ +cson_value * cson_sqlite3_row_to_object( sqlite3_stmt * st ); +/** + Functionally almost identical to cson_sqlite3_row_to_object(), the + only difference being how the result objects gets its column names. + st must be a freshly-step()'d handle holding a result row. + colNames must be an Array with at least the same number of columns + as st. If it has fewer, NULL is returned and this function has + no side-effects. + + For each column in the result set, the colNames entry at the same + index is used for the column key. If a given entry is-not-a String + then conversion will fail and NULL will be returned. + + The one reason to prefer this over cson_sqlite3_row_to_object() is + that this one can share the keys across multiple rows (or even + other JSON containers), whereas the former makes fresh copies of + the column names for each row. + +*/ +cson_value * cson_sqlite3_row_to_object2( sqlite3_stmt * st, + cson_array * colNames ); + +/** + Similar to cson_sqlite3_row_to_object(), but creates an Array + value which contains the JSON-form values of the given result + set row. +*/ +cson_value * cson_sqlite3_row_to_array( sqlite3_stmt * st ); +/** + Converts the results of an sqlite3 SELECT statement to JSON, + in the form of a cson_value object tree. + + st must be a prepared, but not yet traversed, SELECT query. + tgt must be a pointer to NULL (see the example below). If + either of those arguments are NULL, cson_rc.ArgError is returned. + + This walks the query results and returns a JSON object which + has a different structure depending on the value of the 'fat' + argument. + + + If 'fat' is 0 then the structure is: + + @code + { + "columns":["colName1",..."colNameN"], + "rows":[ + [colVal0, ... colValN], + [colVal0, ... colValN], + ... + ] + } + @endcode + + In the "non-fat" format the order of the columns and row values is + guaranteed to be the same as that of the underlying query. + + If 'fat' is not 0 then the structure is: + + @code + { + "columns":["colName1",..."colNameN"], + "rows":[ + {"colName1":value1,..."colNameN":valueN}, + {"colName1":value1,..."colNameN":valueN}, + ... + ] + } + @endcode + + In the "fat" format, the order of the "columns" entries is guaranteed + to be the same as the underlying query fields, but the order + of the keys in the "rows" might be different and might in fact + change when passed through different JSON implementations, + depending on how they implement object key/value pairs. + + On success it returns 0 and assigns *tgt to a newly-allocated + JSON object tree (using the above structure), which the caller owns. + If the query returns no rows, the "rows" value will be an empty + array, as opposed to null. + + On error non-0 is returned and *tgt is not modified. + + The error code cson_rc.IOError is used to indicate a db-level + error, and cson_rc.TypeError is returned if sqlite3_column_count(st) + returns 0 or less (indicating an invalid or non-SELECT statement). + + The JSON data types are determined by the column type as reported + by sqlite3_column_type(): + + SQLITE_INTEGER: integer + + SQLITE_FLOAT: double + + SQLITE_TEXT or SQLITE_BLOB: string, and this will only work if + the data is UTF8 compatible. + + If the db returns a literal or SQL NULL for a value it is converted + to a JSON null. If it somehow finds a column type it cannot handle, + the value is also converted to a NULL in the output. + + Example + + @code + cson_value * json = NULL; + int rc = cson_sqlite3_stmt_to_json( myStatement, &json, 1 ); + if( 0 != rc ) { ... error ... } + else { + cson_output_FILE( json, stdout, NULL ); + cson_value_free( json ); + } + @endcode +*/ +int cson_sqlite3_stmt_to_json( sqlite3_stmt * st, cson_value ** tgt, char fat ); + +/** + A convenience wrapper around cson_sqlite3_stmt_to_json(), which + takes SQL instead of a sqlite3_stmt object. It has the same + return value and argument semantics as that function. +*/ +int cson_sqlite3_sql_to_json( sqlite3 * db, cson_value ** tgt, char const * sql, char fat ); + +/** + Binds a JSON value to a 1-based parameter index in a prepared SQL + statement. v must be NULL or one of one of the types (null, string, + integer, double, boolean, array). Booleans are bound as integer 0 + or 1. NULL or null are bound as SQL NULL. Integers are bound as + 64-bit ints. Strings are bound using sqlite3_bind_text() (as + opposed to text16), but we could/should arguably bind them as + blobs. + + If v is an Array then ndx is is used as a starting position + (1-based) and each item in the array is bound to the next parameter + position (starting and ndx, though the array uses 0-based offsets). + + TODO: add Object support for named parameters. + + Returns 0 on success, non-0 on error. + */ +int cson_sqlite3_bind_value( sqlite3_stmt * st, int ndx, cson_value const * v ); + +#if defined(__cplusplus) +} /*extern "C"*/ +#endif + +#endif /* CSON_ENABLE_SQLITE3 */ +#endif /* WANDERINGHORSE_NET_CSON_SQLITE3_H_INCLUDED */ +/* end file include/wh/cson/cson_sqlite3.h */ +#endif /* FOSSIL_ENABLE_JSON */ ADDED extsrc/linenoise.c Index: extsrc/linenoise.c ================================================================== --- /dev/null +++ extsrc/linenoise.c @@ -0,0 +1,1226 @@ +/* linenoise.c -- guerrilla line editing library against the idea that a + * line editing lib needs to be 20,000 lines of C code. + * + * You can find the latest source code at: + * + * http://github.com/antirez/linenoise + * + * Does a number of crazy assumptions that happen to be true in 99.9999% of + * the 2010 UNIX computers around. + * + * ------------------------------------------------------------------------ + * + * Copyright (c) 2010-2016, Salvatore Sanfilippo + * Copyright (c) 2010-2013, Pieter Noordhuis + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * ------------------------------------------------------------------------ + * + * References: + * - http://invisible-island.net/xterm/ctlseqs/ctlseqs.html + * - http://www.3waylabs.com/nw/WWW/products/wizcon/vt220.html + * + * Todo list: + * - Filter bogus Ctrl+ combinations. + * - Win32 support + * + * Bloat: + * - History search like Ctrl+r in readline? + * + * List of escape sequences used by this program, we do everything just + * with three sequences. In order to be so cheap we may have some + * flickering effect with some slow terminal, but the lesser sequences + * the more compatible. + * + * EL (Erase Line) + * Sequence: ESC [ n K + * Effect: if n is 0 or missing, clear from cursor to end of line + * Effect: if n is 1, clear from beginning of line to cursor + * Effect: if n is 2, clear entire line + * + * CUF (CUrsor Forward) + * Sequence: ESC [ n C + * Effect: moves cursor forward n chars + * + * CUB (CUrsor Backward) + * Sequence: ESC [ n D + * Effect: moves cursor backward n chars + * + * The following is used to get the terminal width if getting + * the width with the TIOCGWINSZ ioctl fails + * + * DSR (Device Status Report) + * Sequence: ESC [ 6 n + * Effect: reports the current cusor position as ESC [ n ; m R + * where n is the row and m is the column + * + * When multi line mode is enabled, we also use an additional escape + * sequence. However multi line editing is disabled by default. + * + * CUU (Cursor Up) + * Sequence: ESC [ n A + * Effect: moves cursor up of n chars. + * + * CUD (Cursor Down) + * Sequence: ESC [ n B + * Effect: moves cursor down of n chars. + * + * When linenoiseClearScreen() is called, two additional escape sequences + * are used in order to clear the screen and position the cursor at home + * position. + * + * CUP (Cursor position) + * Sequence: ESC [ H + * Effect: moves the cursor to upper left corner + * + * ED (Erase display) + * Sequence: ESC [ 2 J + * Effect: clear the whole screen + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "linenoise.h" + +#define LINENOISE_DEFAULT_HISTORY_MAX_LEN 100 +#define LINENOISE_MAX_LINE 4096 +static char *unsupported_term[] = {"dumb","cons25","emacs",NULL}; +static linenoiseCompletionCallback *completionCallback = NULL; +static linenoiseHintsCallback *hintsCallback = NULL; +static linenoiseFreeHintsCallback *freeHintsCallback = NULL; + +static struct termios orig_termios; /* In order to restore at exit.*/ +static int maskmode = 0; /* Show "***" instead of input. For passwords. */ +static int rawmode = 0; /* For atexit() function to check if restore is needed*/ +static int mlmode = 0; /* Multi line mode. Default is single line. */ +static int atexit_registered = 0; /* Register atexit just 1 time. */ +static int history_max_len = LINENOISE_DEFAULT_HISTORY_MAX_LEN; +static int history_len = 0; +static char **history = NULL; + +/* The linenoiseState structure represents the state during line editing. + * We pass this state to functions implementing specific editing + * functionalities. */ +struct linenoiseState { + int ifd; /* Terminal stdin file descriptor. */ + int ofd; /* Terminal stdout file descriptor. */ + char *buf; /* Edited line buffer. */ + size_t buflen; /* Edited line buffer size. */ + const char *prompt; /* Prompt to display. */ + size_t plen; /* Prompt length. */ + size_t pos; /* Current cursor position. */ + size_t oldpos; /* Previous refresh cursor position. */ + size_t len; /* Current edited line length. */ + size_t cols; /* Number of columns in terminal. */ + size_t maxrows; /* Maximum num of rows used so far (multiline mode) */ + int history_index; /* The history index we are currently editing. */ +}; + +enum KEY_ACTION{ + KEY_NULL = 0, /* NULL */ + CTRL_A = 1, /* Ctrl+a */ + CTRL_B = 2, /* Ctrl-b */ + CTRL_C = 3, /* Ctrl-c */ + CTRL_D = 4, /* Ctrl-d */ + CTRL_E = 5, /* Ctrl-e */ + CTRL_F = 6, /* Ctrl-f */ + CTRL_H = 8, /* Ctrl-h */ + TAB = 9, /* Tab */ + CTRL_K = 11, /* Ctrl+k */ + CTRL_L = 12, /* Ctrl+l */ + ENTER = 13, /* Enter */ + CTRL_N = 14, /* Ctrl-n */ + CTRL_P = 16, /* Ctrl-p */ + CTRL_T = 20, /* Ctrl-t */ + CTRL_U = 21, /* Ctrl+u */ + CTRL_W = 23, /* Ctrl+w */ + ESC = 27, /* Escape */ + BACKSPACE = 127 /* Backspace */ +}; + +static void linenoiseAtExit(void); +int linenoiseHistoryAdd(const char *line); +static void refreshLine(struct linenoiseState *l); + +/* Debugging macro. */ +#if 0 +FILE *lndebug_fp = NULL; +#define lndebug(...) \ + do { \ + if (lndebug_fp == NULL) { \ + lndebug_fp = fopen("/tmp/lndebug.txt","a"); \ + fprintf(lndebug_fp, \ + "[%d %d %d] p: %d, rows: %d, rpos: %d, max: %d, oldmax: %d\n", \ + (int)l->len,(int)l->pos,(int)l->oldpos,plen,rows,rpos, \ + (int)l->maxrows,old_rows); \ + } \ + fprintf(lndebug_fp, ", " __VA_ARGS__); \ + fflush(lndebug_fp); \ + } while (0) +#else +#define lndebug(fmt, ...) +#endif + +/* ======================= Low level terminal handling ====================== */ + +/* Enable "mask mode". When it is enabled, instead of the input that + * the user is typing, the terminal will just display a corresponding + * number of asterisks, like "****". This is useful for passwords and other + * secrets that should not be displayed. */ +void linenoiseMaskModeEnable(void) { + maskmode = 1; +} + +/* Disable mask mode. */ +void linenoiseMaskModeDisable(void) { + maskmode = 0; +} + +/* Set if to use or not the multi line mode. */ +void linenoiseSetMultiLine(int ml) { + mlmode = ml; +} + +/* Return true if the terminal name is in the list of terminals we know are + * not able to understand basic escape sequences. */ +static int isUnsupportedTerm(void) { + char *term = getenv("TERM"); + int j; + + if (term == NULL) return 0; + for (j = 0; unsupported_term[j]; j++) + if (!strcasecmp(term,unsupported_term[j])) return 1; + return 0; +} + +/* Raw mode: 1960 magic shit. */ +static int enableRawMode(int fd) { + struct termios raw; + + if (!isatty(STDIN_FILENO)) goto fatal; + if (!atexit_registered) { + atexit(linenoiseAtExit); + atexit_registered = 1; + } + if (tcgetattr(fd,&orig_termios) == -1) goto fatal; + + raw = orig_termios; /* modify the original mode */ + /* input modes: no break, no CR to NL, no parity check, no strip char, + * no start/stop output control. */ + raw.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON); + /* output modes - disable post processing */ + raw.c_oflag &= ~(OPOST); + /* control modes - set 8 bit chars */ + raw.c_cflag |= (CS8); + /* local modes - choing off, canonical off, no extended functions, + * no signal chars (^Z,^C) */ + raw.c_lflag &= ~(ECHO | ICANON | IEXTEN | ISIG); + /* control chars - set return condition: min number of bytes and timer. + * We want read to return every single byte, without timeout. */ + raw.c_cc[VMIN] = 1; raw.c_cc[VTIME] = 0; /* 1 byte, no timer */ + + /* put terminal in raw mode after flushing */ + if (tcsetattr(fd,TCSAFLUSH,&raw) < 0) goto fatal; + rawmode = 1; + return 0; + +fatal: + errno = ENOTTY; + return -1; +} + +static void disableRawMode(int fd) { + /* Don't even check the return value as it's too late. */ + if (rawmode && tcsetattr(fd,TCSAFLUSH,&orig_termios) != -1) + rawmode = 0; +} + +/* Use the ESC [6n escape sequence to query the horizontal cursor position + * and return it. On error -1 is returned, on success the position of the + * cursor. */ +static int getCursorPosition(int ifd, int ofd) { + char buf[32]; + int cols, rows; + unsigned int i = 0; + + /* Report cursor location */ + if (write(ofd, "\x1b[6n", 4) != 4) return -1; + + /* Read the response: ESC [ rows ; cols R */ + while (i < sizeof(buf)-1) { + if (read(ifd,buf+i,1) != 1) break; + if (buf[i] == 'R') break; + i++; + } + buf[i] = '\0'; + + /* Parse it. */ + if (buf[0] != ESC || buf[1] != '[') return -1; + if (sscanf(buf+2,"%d;%d",&rows,&cols) != 2) return -1; + return cols; +} + +/* Try to get the number of columns in the current terminal, or assume 80 + * if it fails. */ +static int getColumns(int ifd, int ofd) { + struct winsize ws; + + if (ioctl(1, TIOCGWINSZ, &ws) == -1 || ws.ws_col == 0) { + /* ioctl() failed. Try to query the terminal itself. */ + int start, cols; + + /* Get the initial position so we can restore it later. */ + start = getCursorPosition(ifd,ofd); + if (start == -1) goto failed; + + /* Go to right margin and get position. */ + if (write(ofd,"\x1b[999C",6) != 6) goto failed; + cols = getCursorPosition(ifd,ofd); + if (cols == -1) goto failed; + + /* Restore position. */ + if (cols > start) { + char seq[32]; + snprintf(seq,32,"\x1b[%dD",cols-start); + if (write(ofd,seq,strlen(seq)) == -1) { + /* Can't recover... */ + } + } + return cols; + } else { + return ws.ws_col; + } + +failed: + return 80; +} + +/* Clear the screen. Used to handle ctrl+l */ +void linenoiseClearScreen(void) { + if (write(STDOUT_FILENO,"\x1b[H\x1b[2J",7) <= 0) { + /* nothing to do, just to avoid warning. */ + } +} + +/* Beep, used for completion when there is nothing to complete or when all + * the choices were already shown. */ +static void linenoiseBeep(void) { + fprintf(stderr, "\x7"); + fflush(stderr); +} + +/* ============================== Completion ================================ */ + +/* Free a list of completion option populated by linenoiseAddCompletion(). */ +static void freeCompletions(linenoiseCompletions *lc) { + size_t i; + for (i = 0; i < lc->len; i++) + free(lc->cvec[i]); + if (lc->cvec != NULL) + free(lc->cvec); +} + +/* This is an helper function for linenoiseEdit() and is called when the + * user types the key in order to complete the string currently in the + * input. + * + * The state of the editing is encapsulated into the pointed linenoiseState + * structure as described in the structure definition. */ +static int completeLine(struct linenoiseState *ls) { + linenoiseCompletions lc = { 0, NULL }; + int nread, nwritten; + char c = 0; + + completionCallback(ls->buf,&lc); + if (lc.len == 0) { + linenoiseBeep(); + } else { + size_t stop = 0, i = 0; + + while(!stop) { + /* Show completion or original buffer */ + if (i < lc.len) { + struct linenoiseState saved = *ls; + + ls->len = ls->pos = strlen(lc.cvec[i]); + ls->buf = lc.cvec[i]; + refreshLine(ls); + ls->len = saved.len; + ls->pos = saved.pos; + ls->buf = saved.buf; + } else { + refreshLine(ls); + } + + nread = read(ls->ifd,&c,1); + if (nread <= 0) { + freeCompletions(&lc); + return -1; + } + + switch(c) { + case 9: /* tab */ + i = (i+1) % (lc.len+1); + if (i == lc.len) linenoiseBeep(); + break; + case 27: /* escape */ + /* Re-show original buffer */ + if (i < lc.len) refreshLine(ls); + stop = 1; + break; + default: + /* Update buffer and return */ + if (i < lc.len) { + nwritten = snprintf(ls->buf,ls->buflen,"%s",lc.cvec[i]); + ls->len = ls->pos = nwritten; + } + stop = 1; + break; + } + } + } + + freeCompletions(&lc); + return c; /* Return last read character */ +} + +/* Register a callback function to be called for tab-completion. */ +void linenoiseSetCompletionCallback(linenoiseCompletionCallback *fn) { + completionCallback = fn; +} + +/* Register a hits function to be called to show hits to the user at the + * right of the prompt. */ +void linenoiseSetHintsCallback(linenoiseHintsCallback *fn) { + hintsCallback = fn; +} + +/* Register a function to free the hints returned by the hints callback + * registered with linenoiseSetHintsCallback(). */ +void linenoiseSetFreeHintsCallback(linenoiseFreeHintsCallback *fn) { + freeHintsCallback = fn; +} + +/* This function is used by the callback function registered by the user + * in order to add completion options given the input string when the + * user typed . See the example.c source code for a very easy to + * understand example. */ +void linenoiseAddCompletion(linenoiseCompletions *lc, const char *str) { + size_t len = strlen(str); + char *copy, **cvec; + + copy = malloc(len+1); + if (copy == NULL) return; + memcpy(copy,str,len+1); + cvec = realloc(lc->cvec,sizeof(char*)*(lc->len+1)); + if (cvec == NULL) { + free(copy); + return; + } + lc->cvec = cvec; + lc->cvec[lc->len++] = copy; +} + +/* =========================== Line editing ================================= */ + +/* We define a very simple "append buffer" structure, that is an heap + * allocated string where we can append to. This is useful in order to + * write all the escape sequences in a buffer and flush them to the standard + * output in a single call, to avoid flickering effects. */ +struct abuf { + char *b; + int len; +}; + +static void abInit(struct abuf *ab) { + ab->b = NULL; + ab->len = 0; +} + +static void abAppend(struct abuf *ab, const char *s, int len) { + char *new = realloc(ab->b,ab->len+len); + + if (new == NULL) return; + memcpy(new+ab->len,s,len); + ab->b = new; + ab->len += len; +} + +static void abFree(struct abuf *ab) { + free(ab->b); +} + +/* Helper of refreshSingleLine() and refreshMultiLine() to show hints + * to the right of the prompt. */ +void refreshShowHints(struct abuf *ab, struct linenoiseState *l, int plen) { + char seq[64]; + if (hintsCallback && plen+l->len < l->cols) { + int color = -1, bold = 0; + char *hint = hintsCallback(l->buf,&color,&bold); + if (hint) { + int hintlen = strlen(hint); + int hintmaxlen = l->cols-(plen+l->len); + if (hintlen > hintmaxlen) hintlen = hintmaxlen; + if (bold == 1 && color == -1) color = 37; + if (color != -1 || bold != 0) + snprintf(seq,64,"\033[%d;%d;49m",bold,color); + else + seq[0] = '\0'; + abAppend(ab,seq,strlen(seq)); + abAppend(ab,hint,hintlen); + if (color != -1 || bold != 0) + abAppend(ab,"\033[0m",4); + /* Call the function to free the hint returned. */ + if (freeHintsCallback) freeHintsCallback(hint); + } + } +} + +/* Single line low level line refresh. + * + * Rewrite the currently edited line accordingly to the buffer content, + * cursor position, and number of columns of the terminal. */ +static void refreshSingleLine(struct linenoiseState *l) { + char seq[64]; + size_t plen = strlen(l->prompt); + int fd = l->ofd; + char *buf = l->buf; + size_t len = l->len; + size_t pos = l->pos; + struct abuf ab; + + while((plen+pos) >= l->cols) { + buf++; + len--; + pos--; + } + while (plen+len > l->cols) { + len--; + } + + abInit(&ab); + /* Cursor to left edge */ + snprintf(seq,64,"\r"); + abAppend(&ab,seq,strlen(seq)); + /* Write the prompt and the current buffer content */ + abAppend(&ab,l->prompt,strlen(l->prompt)); + if (maskmode == 1) { + while (len--) abAppend(&ab,"*",1); + } else { + abAppend(&ab,buf,len); + } + /* Show hits if any. */ + refreshShowHints(&ab,l,plen); + /* Erase to right */ + snprintf(seq,64,"\x1b[0K"); + abAppend(&ab,seq,strlen(seq)); + /* Move cursor to original position. */ + snprintf(seq,64,"\r\x1b[%dC", (int)(pos+plen)); + abAppend(&ab,seq,strlen(seq)); + if (write(fd,ab.b,ab.len) == -1) {} /* Can't recover from write error. */ + abFree(&ab); +} + +/* Multi line low level line refresh. + * + * Rewrite the currently edited line accordingly to the buffer content, + * cursor position, and number of columns of the terminal. */ +static void refreshMultiLine(struct linenoiseState *l) { + char seq[64]; + int plen = strlen(l->prompt); + int rows = (plen+l->len+l->cols-1)/l->cols; /* rows used by current buf. */ + int rpos = (plen+l->oldpos+l->cols)/l->cols; /* cursor relative row. */ + int rpos2; /* rpos after refresh. */ + int col; /* colum position, zero-based. */ + int old_rows = l->maxrows; + int fd = l->ofd, j; + struct abuf ab; + + /* Update maxrows if needed. */ + if (rows > (int)l->maxrows) l->maxrows = rows; + + /* First step: clear all the lines used before. To do so start by + * going to the last row. */ + abInit(&ab); + if (old_rows-rpos > 0) { + lndebug("go down %d", old_rows-rpos); + snprintf(seq,64,"\x1b[%dB", old_rows-rpos); + abAppend(&ab,seq,strlen(seq)); + } + + /* Now for every row clear it, go up. */ + for (j = 0; j < old_rows-1; j++) { + lndebug("clear+up"); + snprintf(seq,64,"\r\x1b[0K\x1b[1A"); + abAppend(&ab,seq,strlen(seq)); + } + + /* Clean the top line. */ + lndebug("clear"); + snprintf(seq,64,"\r\x1b[0K"); + abAppend(&ab,seq,strlen(seq)); + + /* Write the prompt and the current buffer content */ + abAppend(&ab,l->prompt,strlen(l->prompt)); + if (maskmode == 1) { + unsigned int i; + for (i = 0; i < l->len; i++) abAppend(&ab,"*",1); + } else { + abAppend(&ab,l->buf,l->len); + } + + /* Show hits if any. */ + refreshShowHints(&ab,l,plen); + + /* If we are at the very end of the screen with our prompt, we need to + * emit a newline and move the prompt to the first column. */ + if (l->pos && + l->pos == l->len && + (l->pos+plen) % l->cols == 0) + { + lndebug(""); + abAppend(&ab,"\n",1); + snprintf(seq,64,"\r"); + abAppend(&ab,seq,strlen(seq)); + rows++; + if (rows > (int)l->maxrows) l->maxrows = rows; + } + + /* Move cursor to right position. */ + rpos2 = (plen+l->pos+l->cols)/l->cols; /* current cursor relative row. */ + lndebug("rpos2 %d", rpos2); + + /* Go up till we reach the expected positon. */ + if (rows-rpos2 > 0) { + lndebug("go-up %d", rows-rpos2); + snprintf(seq,64,"\x1b[%dA", rows-rpos2); + abAppend(&ab,seq,strlen(seq)); + } + + /* Set column. */ + col = (plen+(int)l->pos) % (int)l->cols; + lndebug("set col %d", 1+col); + if (col) + snprintf(seq,64,"\r\x1b[%dC", col); + else + snprintf(seq,64,"\r"); + abAppend(&ab,seq,strlen(seq)); + + lndebug("\n"); + l->oldpos = l->pos; + + if (write(fd,ab.b,ab.len) == -1) {} /* Can't recover from write error. */ + abFree(&ab); +} + +/* Calls the two low level functions refreshSingleLine() or + * refreshMultiLine() according to the selected mode. */ +static void refreshLine(struct linenoiseState *l) { + if (mlmode) + refreshMultiLine(l); + else + refreshSingleLine(l); +} + +/* Insert the character 'c' at cursor current position. + * + * On error writing to the terminal -1 is returned, otherwise 0. */ +int linenoiseEditInsert(struct linenoiseState *l, char c) { + if (l->len < l->buflen) { + if (l->len == l->pos) { + l->buf[l->pos] = c; + l->pos++; + l->len++; + l->buf[l->len] = '\0'; + if ((!mlmode && l->plen+l->len < l->cols && !hintsCallback)) { + /* Avoid a full update of the line in the + * trivial case. */ + char d = (maskmode==1) ? '*' : c; + if (write(l->ofd,&d,1) == -1) return -1; + } else { + refreshLine(l); + } + } else { + memmove(l->buf+l->pos+1,l->buf+l->pos,l->len-l->pos); + l->buf[l->pos] = c; + l->len++; + l->pos++; + l->buf[l->len] = '\0'; + refreshLine(l); + } + } + return 0; +} + +/* Move cursor on the left. */ +void linenoiseEditMoveLeft(struct linenoiseState *l) { + if (l->pos > 0) { + l->pos--; + refreshLine(l); + } +} + +/* Move cursor on the right. */ +void linenoiseEditMoveRight(struct linenoiseState *l) { + if (l->pos != l->len) { + l->pos++; + refreshLine(l); + } +} + +/* Move cursor to the start of the line. */ +void linenoiseEditMoveHome(struct linenoiseState *l) { + if (l->pos != 0) { + l->pos = 0; + refreshLine(l); + } +} + +/* Move cursor to the end of the line. */ +void linenoiseEditMoveEnd(struct linenoiseState *l) { + if (l->pos != l->len) { + l->pos = l->len; + refreshLine(l); + } +} + +/* Substitute the currently edited line with the next or previous history + * entry as specified by 'dir'. */ +#define LINENOISE_HISTORY_NEXT 0 +#define LINENOISE_HISTORY_PREV 1 +void linenoiseEditHistoryNext(struct linenoiseState *l, int dir) { + if (history_len > 1) { + /* Update the current history entry before to + * overwrite it with the next one. */ + free(history[history_len - 1 - l->history_index]); + history[history_len - 1 - l->history_index] = strdup(l->buf); + /* Show the new entry */ + l->history_index += (dir == LINENOISE_HISTORY_PREV) ? 1 : -1; + if (l->history_index < 0) { + l->history_index = 0; + return; + } else if (l->history_index >= history_len) { + l->history_index = history_len-1; + return; + } + strncpy(l->buf,history[history_len - 1 - l->history_index],l->buflen); + l->buf[l->buflen-1] = '\0'; + l->len = l->pos = strlen(l->buf); + refreshLine(l); + } +} + +/* Delete the character at the right of the cursor without altering the cursor + * position. Basically this is what happens with the "Delete" keyboard key. */ +void linenoiseEditDelete(struct linenoiseState *l) { + if (l->len > 0 && l->pos < l->len) { + memmove(l->buf+l->pos,l->buf+l->pos+1,l->len-l->pos-1); + l->len--; + l->buf[l->len] = '\0'; + refreshLine(l); + } +} + +/* Backspace implementation. */ +void linenoiseEditBackspace(struct linenoiseState *l) { + if (l->pos > 0 && l->len > 0) { + memmove(l->buf+l->pos-1,l->buf+l->pos,l->len-l->pos); + l->pos--; + l->len--; + l->buf[l->len] = '\0'; + refreshLine(l); + } +} + +/* Delete the previosu word, maintaining the cursor at the start of the + * current word. */ +void linenoiseEditDeletePrevWord(struct linenoiseState *l) { + size_t old_pos = l->pos; + size_t diff; + + while (l->pos > 0 && l->buf[l->pos-1] == ' ') + l->pos--; + while (l->pos > 0 && l->buf[l->pos-1] != ' ') + l->pos--; + diff = old_pos - l->pos; + memmove(l->buf+l->pos,l->buf+old_pos,l->len-old_pos+1); + l->len -= diff; + refreshLine(l); +} + +/* This function is the core of the line editing capability of linenoise. + * It expects 'fd' to be already in "raw mode" so that every key pressed + * will be returned ASAP to read(). + * + * The resulting string is put into 'buf' when the user type enter, or + * when ctrl+d is typed. + * + * The function returns the length of the current buffer. */ +static int linenoiseEdit(int stdin_fd, int stdout_fd, char *buf, size_t buflen, const char *prompt) +{ + struct linenoiseState l; + + /* Populate the linenoise state that we pass to functions implementing + * specific editing functionalities. */ + l.ifd = stdin_fd; + l.ofd = stdout_fd; + l.buf = buf; + l.buflen = buflen; + l.prompt = prompt; + l.plen = strlen(prompt); + l.oldpos = l.pos = 0; + l.len = 0; + l.cols = getColumns(stdin_fd, stdout_fd); + l.maxrows = 0; + l.history_index = 0; + + /* Buffer starts empty. */ + l.buf[0] = '\0'; + l.buflen--; /* Make sure there is always space for the nulterm */ + + /* The latest history entry is always our current buffer, that + * initially is just an empty string. */ + linenoiseHistoryAdd(""); + + if (write(l.ofd,prompt,l.plen) == -1) return -1; + while(1) { + char c; + int nread; + char seq[3]; + + nread = read(l.ifd,&c,1); + if (nread <= 0) return l.len; + + /* Only autocomplete when the callback is set. It returns < 0 when + * there was an error reading from fd. Otherwise it will return the + * character that should be handled next. */ + if (c == 9 && completionCallback != NULL) { + c = completeLine(&l); + /* Return on errors */ + if (c < 0) return l.len; + /* Read next character when 0 */ + if (c == 0) continue; + } + + switch(c) { + case ENTER: /* enter */ + history_len--; + free(history[history_len]); + if (mlmode) linenoiseEditMoveEnd(&l); + if (hintsCallback) { + /* Force a refresh without hints to leave the previous + * line as the user typed it after a newline. */ + linenoiseHintsCallback *hc = hintsCallback; + hintsCallback = NULL; + refreshLine(&l); + hintsCallback = hc; + } + return (int)l.len; + case CTRL_C: /* ctrl-c */ + errno = EAGAIN; + return -1; + case BACKSPACE: /* backspace */ + case 8: /* ctrl-h */ + linenoiseEditBackspace(&l); + break; + case CTRL_D: /* ctrl-d, remove char at right of cursor, or if the + line is empty, act as end-of-file. */ + if (l.len > 0) { + linenoiseEditDelete(&l); + } else { + history_len--; + free(history[history_len]); + return -1; + } + break; + case CTRL_T: /* ctrl-t, swaps current character with previous. */ + if (l.pos > 0 && l.pos < l.len) { + int aux = buf[l.pos-1]; + buf[l.pos-1] = buf[l.pos]; + buf[l.pos] = aux; + if (l.pos != l.len-1) l.pos++; + refreshLine(&l); + } + break; + case CTRL_B: /* ctrl-b */ + linenoiseEditMoveLeft(&l); + break; + case CTRL_F: /* ctrl-f */ + linenoiseEditMoveRight(&l); + break; + case CTRL_P: /* ctrl-p */ + linenoiseEditHistoryNext(&l, LINENOISE_HISTORY_PREV); + break; + case CTRL_N: /* ctrl-n */ + linenoiseEditHistoryNext(&l, LINENOISE_HISTORY_NEXT); + break; + case ESC: /* escape sequence */ + /* Read the next two bytes representing the escape sequence. + * Use two calls to handle slow terminals returning the two + * chars at different times. */ + if (read(l.ifd,seq,1) == -1) break; + if (read(l.ifd,seq+1,1) == -1) break; + + /* ESC [ sequences. */ + if (seq[0] == '[') { + if (seq[1] >= '0' && seq[1] <= '9') { + /* Extended escape, read additional byte. */ + if (read(l.ifd,seq+2,1) == -1) break; + if (seq[2] == '~') { + switch(seq[1]) { + case '3': /* Delete key. */ + linenoiseEditDelete(&l); + break; + } + } + } else { + switch(seq[1]) { + case 'A': /* Up */ + linenoiseEditHistoryNext(&l, LINENOISE_HISTORY_PREV); + break; + case 'B': /* Down */ + linenoiseEditHistoryNext(&l, LINENOISE_HISTORY_NEXT); + break; + case 'C': /* Right */ + linenoiseEditMoveRight(&l); + break; + case 'D': /* Left */ + linenoiseEditMoveLeft(&l); + break; + case 'H': /* Home */ + linenoiseEditMoveHome(&l); + break; + case 'F': /* End*/ + linenoiseEditMoveEnd(&l); + break; + } + } + } + + /* ESC O sequences. */ + else if (seq[0] == 'O') { + switch(seq[1]) { + case 'H': /* Home */ + linenoiseEditMoveHome(&l); + break; + case 'F': /* End*/ + linenoiseEditMoveEnd(&l); + break; + } + } + break; + default: + if (linenoiseEditInsert(&l,c)) return -1; + break; + case CTRL_U: /* Ctrl+u, delete the whole line. */ + buf[0] = '\0'; + l.pos = l.len = 0; + refreshLine(&l); + break; + case CTRL_K: /* Ctrl+k, delete from current to end of line. */ + buf[l.pos] = '\0'; + l.len = l.pos; + refreshLine(&l); + break; + case CTRL_A: /* Ctrl+a, go to the start of the line */ + linenoiseEditMoveHome(&l); + break; + case CTRL_E: /* ctrl+e, go to the end of the line */ + linenoiseEditMoveEnd(&l); + break; + case CTRL_L: /* ctrl+l, clear screen */ + linenoiseClearScreen(); + refreshLine(&l); + break; + case CTRL_W: /* ctrl+w, delete previous word */ + linenoiseEditDeletePrevWord(&l); + break; + } + } + return l.len; +} + +/* This special mode is used by linenoise in order to print scan codes + * on screen for debugging / development purposes. It is implemented + * by the linenoise_example program using the --keycodes option. */ +void linenoisePrintKeyCodes(void) { + char quit[4]; + + printf("Linenoise key codes debugging mode.\n" + "Press keys to see scan codes. Type 'quit' at any time to exit.\n"); + if (enableRawMode(STDIN_FILENO) == -1) return; + memset(quit,' ',4); + while(1) { + char c; + int nread; + + nread = read(STDIN_FILENO,&c,1); + if (nread <= 0) continue; + memmove(quit,quit+1,sizeof(quit)-1); /* shift string to left. */ + quit[sizeof(quit)-1] = c; /* Insert current char on the right. */ + if (memcmp(quit,"quit",sizeof(quit)) == 0) break; + + printf("'%c' %02x (%d) (type quit to exit)\n", + isprint(c) ? c : '?', (int)c, (int)c); + printf("\r"); /* Go left edge manually, we are in raw mode. */ + fflush(stdout); + } + disableRawMode(STDIN_FILENO); +} + +/* This function calls the line editing function linenoiseEdit() using + * the STDIN file descriptor set in raw mode. */ +static int linenoiseRaw(char *buf, size_t buflen, const char *prompt) { + int count; + + if (buflen == 0) { + errno = EINVAL; + return -1; + } + + if (enableRawMode(STDIN_FILENO) == -1) return -1; + count = linenoiseEdit(STDIN_FILENO, STDOUT_FILENO, buf, buflen, prompt); + disableRawMode(STDIN_FILENO); + printf("\n"); + return count; +} + +/* This function is called when linenoise() is called with the standard + * input file descriptor not attached to a TTY. So for example when the + * program using linenoise is called in pipe or with a file redirected + * to its standard input. In this case, we want to be able to return the + * line regardless of its length (by default we are limited to 4k). */ +static char *linenoiseNoTTY(void) { + char *line = NULL; + size_t len = 0, maxlen = 0; + + while(1) { + int c; + if (len == maxlen) { + char *oldval = line; + if (maxlen == 0) maxlen = 16; + maxlen *= 2; + line = realloc(line,maxlen); + if (line == NULL) { + if (oldval) free(oldval); + return NULL; + } + } + c = fgetc(stdin); + if (c == EOF || c == '\n') { + if (c == EOF && len == 0) { + free(line); + return NULL; + } else { + line[len] = '\0'; + return line; + } + } else { + line[len] = c; + len++; + } + } +} + +/* The high level function that is the main API of the linenoise library. + * This function checks if the terminal has basic capabilities, just checking + * for a blacklist of stupid terminals, and later either calls the line + * editing function or uses dummy fgets() so that you will be able to type + * something even in the most desperate of the conditions. */ +char *linenoise(const char *prompt) { + char buf[LINENOISE_MAX_LINE]; + int count; + + if (!isatty(STDIN_FILENO)) { + /* Not a tty: read from file / pipe. In this mode we don't want any + * limit to the line size, so we call a function to handle that. */ + return linenoiseNoTTY(); + } else if (isUnsupportedTerm()) { + size_t len; + + printf("%s",prompt); + fflush(stdout); + if (fgets(buf,LINENOISE_MAX_LINE,stdin) == NULL) return NULL; + len = strlen(buf); + while(len && (buf[len-1] == '\n' || buf[len-1] == '\r')) { + len--; + buf[len] = '\0'; + } + return strdup(buf); + } else { + count = linenoiseRaw(buf,LINENOISE_MAX_LINE,prompt); + if (count == -1) return NULL; + return strdup(buf); + } +} + +/* This is just a wrapper the user may want to call in order to make sure + * the linenoise returned buffer is freed with the same allocator it was + * created with. Useful when the main program is using an alternative + * allocator. */ +void linenoiseFree(void *ptr) { + free(ptr); +} + +/* ================================ History ================================= */ + +/* Free the history, but does not reset it. Only used when we have to + * exit() to avoid memory leaks are reported by valgrind & co. */ +static void freeHistory(void) { + if (history) { + int j; + + for (j = 0; j < history_len; j++) + free(history[j]); + free(history); + } +} + +/* At exit we'll try to fix the terminal to the initial conditions. */ +static void linenoiseAtExit(void) { + disableRawMode(STDIN_FILENO); + freeHistory(); +} + +/* This is the API call to add a new entry in the linenoise history. + * It uses a fixed array of char pointers that are shifted (memmoved) + * when the history max length is reached in order to remove the older + * entry and make room for the new one, so it is not exactly suitable for huge + * histories, but will work well for a few hundred of entries. + * + * Using a circular buffer is smarter, but a bit more complex to handle. */ +int linenoiseHistoryAdd(const char *line) { + char *linecopy; + + if (history_max_len == 0) return 0; + + /* Initialization on first call. */ + if (history == NULL) { + history = malloc(sizeof(char*)*history_max_len); + if (history == NULL) return 0; + memset(history,0,(sizeof(char*)*history_max_len)); + } + + /* Don't add duplicated lines. */ + if (history_len && !strcmp(history[history_len-1], line)) return 0; + + /* Add an heap allocated copy of the line in the history. + * If we reached the max length, remove the older line. */ + linecopy = strdup(line); + if (!linecopy) return 0; + if (history_len == history_max_len) { + free(history[0]); + memmove(history,history+1,sizeof(char*)*(history_max_len-1)); + history_len--; + } + history[history_len] = linecopy; + history_len++; + return 1; +} + +/* Set the maximum length for the history. This function can be called even + * if there is already some history, the function will make sure to retain + * just the latest 'len' elements if the new history length value is smaller + * than the amount of items already inside the history. */ +int linenoiseHistorySetMaxLen(int len) { + char **new; + + if (len < 1) return 0; + if (history) { + int tocopy = history_len; + + new = malloc(sizeof(char*)*len); + if (new == NULL) return 0; + + /* If we can't copy everything, free the elements we'll not use. */ + if (len < tocopy) { + int j; + + for (j = 0; j < tocopy-len; j++) free(history[j]); + tocopy = len; + } + memset(new,0,sizeof(char*)*len); + memcpy(new,history+(history_len-tocopy), sizeof(char*)*tocopy); + free(history); + history = new; + } + history_max_len = len; + if (history_len > history_max_len) + history_len = history_max_len; + return 1; +} + +/* Save the history in the specified file. On success 0 is returned + * otherwise -1 is returned. */ +int linenoiseHistorySave(const char *filename) { + mode_t old_umask = umask(S_IXUSR|S_IRWXG|S_IRWXO); + FILE *fp; + int j; + + fp = fopen(filename,"w"); + umask(old_umask); + if (fp == NULL) return -1; + chmod(filename,S_IRUSR|S_IWUSR); + for (j = 0; j < history_len; j++) + fprintf(fp,"%s\n",history[j]); + fclose(fp); + return 0; +} + +/* Load the history from the specified file. If the file does not exist + * zero is returned and no operation is performed. + * + * If the file exists and the operation succeeded 0 is returned, otherwise + * on error -1 is returned. */ +int linenoiseHistoryLoad(const char *filename) { + FILE *fp = fopen(filename,"r"); + char buf[LINENOISE_MAX_LINE]; + + if (fp == NULL) return -1; + + while (fgets(buf,LINENOISE_MAX_LINE,fp) != NULL) { + char *p; + + p = strchr(buf,'\r'); + if (!p) p = strchr(buf,'\n'); + if (p) *p = '\0'; + linenoiseHistoryAdd(buf); + } + fclose(fp); + return 0; +} ADDED extsrc/linenoise.h Index: extsrc/linenoise.h ================================================================== --- /dev/null +++ extsrc/linenoise.h @@ -0,0 +1,75 @@ +/* linenoise.h -- VERSION 1.0 + * + * Guerrilla line editing library against the idea that a line editing lib + * needs to be 20,000 lines of C code. + * + * See linenoise.c for more information. + * + * ------------------------------------------------------------------------ + * + * Copyright (c) 2010-2014, Salvatore Sanfilippo + * Copyright (c) 2010-2013, Pieter Noordhuis + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef __LINENOISE_H +#define __LINENOISE_H + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct linenoiseCompletions { + size_t len; + char **cvec; +} linenoiseCompletions; + +typedef void(linenoiseCompletionCallback)(const char *, linenoiseCompletions *); +typedef char*(linenoiseHintsCallback)(const char *, int *color, int *bold); +typedef void(linenoiseFreeHintsCallback)(void *); +void linenoiseSetCompletionCallback(linenoiseCompletionCallback *); +void linenoiseSetHintsCallback(linenoiseHintsCallback *); +void linenoiseSetFreeHintsCallback(linenoiseFreeHintsCallback *); +void linenoiseAddCompletion(linenoiseCompletions *, const char *); + +char *linenoise(const char *prompt); +void linenoiseFree(void *ptr); +int linenoiseHistoryAdd(const char *line); +int linenoiseHistorySetMaxLen(int len); +int linenoiseHistorySave(const char *filename); +int linenoiseHistoryLoad(const char *filename); +void linenoiseClearScreen(void); +void linenoiseSetMultiLine(int ml); +void linenoisePrintKeyCodes(void); +void linenoiseMaskModeEnable(void); +void linenoiseMaskModeDisable(void); + +#ifdef __cplusplus +} +#endif + +#endif /* __LINENOISE_H */ ADDED extsrc/miniz.c Index: extsrc/miniz.c ================================================================== --- /dev/null +++ extsrc/miniz.c @@ -0,0 +1,4916 @@ +/* miniz.c v1.15 - public domain deflate/inflate, zlib-subset, ZIP reading/writing/appending, PNG writing + See "unlicense" statement at the end of this file. + Rich Geldreich , last updated Oct. 13, 2013 + Implements RFC 1950: http://www.ietf.org/rfc/rfc1950.txt and RFC 1951: http://www.ietf.org/rfc/rfc1951.txt + + Most API's defined in miniz.c are optional. For example, to disable the archive related functions just define + MINIZ_NO_ARCHIVE_APIS, or to get rid of all stdio usage define MINIZ_NO_STDIO (see the list below for more macros). + + * Change History + 10/13/13 v1.15 r4 - Interim bugfix release while I work on the next major release with Zip64 support (almost there!): + - Critical fix for the MZ_ZIP_FLAG_DO_NOT_SORT_CENTRAL_DIRECTORY bug (thanks kahmyong.moon@hp.com) which could cause locate files to not find files. This bug + would only have occured in earlier versions if you explicitly used this flag, OR if you used mz_zip_extract_archive_file_to_heap() or mz_zip_add_mem_to_archive_file_in_place() + (which used this flag). If you can't switch to v1.15 but want to fix this bug, just remove the uses of this flag from both helper funcs (and of course don't use the flag). + - Bugfix in mz_zip_reader_extract_to_mem_no_alloc() from kymoon when pUser_read_buf is not NULL and compressed size is > uncompressed size + - Fixing mz_zip_reader_extract_*() funcs so they don't try to extract compressed data from directory entries, to account for weird zipfiles which contain zero-size compressed data on dir entries. + Hopefully this fix won't cause any issues on weird zip archives, because it assumes the low 16-bits of zip external attributes are DOS attributes (which I believe they always are in practice). + - Fixing mz_zip_reader_is_file_a_directory() so it doesn't check the internal attributes, just the filename and external attributes + - mz_zip_reader_init_file() - missing MZ_FCLOSE() call if the seek failed + - Added cmake support for Linux builds which builds all the examples, tested with clang v3.3 and gcc v4.6. + - Clang fix for tdefl_write_image_to_png_file_in_memory() from toffaletti + - Merged MZ_FORCEINLINE fix from hdeanclark + - Fix include before config #ifdef, thanks emil.brink + - Added tdefl_write_image_to_png_file_in_memory_ex(): supports Y flipping (super useful for OpenGL apps), and explicit control over the compression level (so you can + set it to 1 for real-time compression). + - Merged in some compiler fixes from paulharris's github repro. + - Retested this build under Windows (VS 2010, including static analysis), tcc 0.9.26, gcc v4.6 and clang v3.3. + - Added example6.c, which dumps an image of the mandelbrot set to a PNG file. + - Modified example2 to help test the MZ_ZIP_FLAG_DO_NOT_SORT_CENTRAL_DIRECTORY flag more. + - In r3: Bugfix to mz_zip_writer_add_file() found during merge: Fix possible src file fclose() leak if alignment bytes+local header file write faiiled + - In r4: Minor bugfix to mz_zip_writer_add_from_zip_reader(): Was pushing the wrong central dir header offset, appears harmless in this release, but it became a problem in the zip64 branch + 5/20/12 v1.14 - MinGW32/64 GCC 4.6.1 compiler fixes: added MZ_FORCEINLINE, #include (thanks fermtect). + 5/19/12 v1.13 - From jason@cornsyrup.org and kelwert@mtu.edu - Fix mz_crc32() so it doesn't compute the wrong CRC-32's when mz_ulong is 64-bit. + - Temporarily/locally slammed in "typedef unsigned long mz_ulong" and re-ran a randomized regression test on ~500k files. + - Eliminated a bunch of warnings when compiling with GCC 32-bit/64. + - Ran all examples, miniz.c, and tinfl.c through MSVC 2008's /analyze (static analysis) option and fixed all warnings (except for the silly + "Use of the comma-operator in a tested expression.." analysis warning, which I purposely use to work around a MSVC compiler warning). + - Created 32-bit and 64-bit Codeblocks projects/workspace. Built and tested Linux executables. The codeblocks workspace is compatible with Linux+Win32/x64. + - Added miniz_tester solution/project, which is a useful little app derived from LZHAM's tester app that I use as part of the regression test. + - Ran miniz.c and tinfl.c through another series of regression testing on ~500,000 files and archives. + - Modified example5.c so it purposely disables a bunch of high-level functionality (MINIZ_NO_STDIO, etc.). (Thanks to corysama for the MINIZ_NO_STDIO bug report.) + - Fix ftell() usage in examples so they exit with an error on files which are too large (a limitation of the examples, not miniz itself). + 4/12/12 v1.12 - More comments, added low-level example5.c, fixed a couple minor level_and_flags issues in the archive API's. + level_and_flags can now be set to MZ_DEFAULT_COMPRESSION. Thanks to Bruce Dawson for the feedback/bug report. + 5/28/11 v1.11 - Added statement from unlicense.org + 5/27/11 v1.10 - Substantial compressor optimizations: + - Level 1 is now ~4x faster than before. The L1 compressor's throughput now varies between 70-110MB/sec. on a + - Core i7 (actual throughput varies depending on the type of data, and x64 vs. x86). + - Improved baseline L2-L9 compression perf. Also, greatly improved compression perf. issues on some file types. + - Refactored the compression code for better readability and maintainability. + - Added level 10 compression level (L10 has slightly better ratio than level 9, but could have a potentially large + drop in throughput on some files). + 5/15/11 v1.09 - Initial stable release. + + * Low-level Deflate/Inflate implementation notes: + + Compression: Use the "tdefl" API's. The compressor supports raw, static, and dynamic blocks, lazy or + greedy parsing, match length filtering, RLE-only, and Huffman-only streams. It performs and compresses + approximately as well as zlib. + + Decompression: Use the "tinfl" API's. The entire decompressor is implemented as a single function + coroutine: see tinfl_decompress(). It supports decompression into a 32KB (or larger power of 2) wrapping buffer, or into a memory + block large enough to hold the entire file. + + The low-level tdefl/tinfl API's do not make any use of dynamic memory allocation. + + * zlib-style API notes: + + miniz.c implements a fairly large subset of zlib. There's enough functionality present for it to be a drop-in + zlib replacement in many apps: + The z_stream struct, optional memory allocation callbacks + deflateInit/deflateInit2/deflate/deflateReset/deflateEnd/deflateBound + inflateInit/inflateInit2/inflate/inflateEnd + compress, compress2, compressBound, uncompress + CRC-32, Adler-32 - Using modern, minimal code size, CPU cache friendly routines. + Supports raw deflate streams or standard zlib streams with adler-32 checking. + + Limitations: + The callback API's are not implemented yet. No support for gzip headers or zlib static dictionaries. + I've tried to closely emulate zlib's various flavors of stream flushing and return status codes, but + there are no guarantees that miniz.c pulls this off perfectly. + + * PNG writing: See the tdefl_write_image_to_png_file_in_memory() function, originally written by + Alex Evans. Supports 1-4 bytes/pixel images. + + * ZIP archive API notes: + + The ZIP archive API's where designed with simplicity and efficiency in mind, with just enough abstraction to + get the job done with minimal fuss. There are simple API's to retrieve file information, read files from + existing archives, create new archives, append new files to existing archives, or clone archive data from + one archive to another. It supports archives located in memory or the heap, on disk (using stdio.h), + or you can specify custom file read/write callbacks. + + - Archive reading: Just call this function to read a single file from a disk archive: + + void *mz_zip_extract_archive_file_to_heap(const char *pZip_filename, const char *pArchive_name, + size_t *pSize, mz_uint zip_flags); + + For more complex cases, use the "mz_zip_reader" functions. Upon opening an archive, the entire central + directory is located and read as-is into memory, and subsequent file access only occurs when reading individual files. + + - Archives file scanning: The simple way is to use this function to scan a loaded archive for a specific file: + + int mz_zip_reader_locate_file(mz_zip_archive *pZip, const char *pName, const char *pComment, mz_uint flags); + + The locate operation can optionally check file comments too, which (as one example) can be used to identify + multiple versions of the same file in an archive. This function uses a simple linear search through the central + directory, so it's not very fast. + + Alternately, you can iterate through all the files in an archive (using mz_zip_reader_get_num_files()) and + retrieve detailed info on each file by calling mz_zip_reader_file_stat(). + + - Archive creation: Use the "mz_zip_writer" functions. The ZIP writer immediately writes compressed file data + to disk and builds an exact image of the central directory in memory. The central directory image is written + all at once at the end of the archive file when the archive is finalized. + + The archive writer can optionally align each file's local header and file data to any power of 2 alignment, + which can be useful when the archive will be read from optical media. Also, the writer supports placing + arbitrary data blobs at the very beginning of ZIP archives. Archives written using either feature are still + readable by any ZIP tool. + + - Archive appending: The simple way to add a single file to an archive is to call this function: + + mz_bool mz_zip_add_mem_to_archive_file_in_place(const char *pZip_filename, const char *pArchive_name, + const void *pBuf, size_t buf_size, const void *pComment, mz_uint16 comment_size, mz_uint level_and_flags); + + The archive will be created if it doesn't already exist, otherwise it'll be appended to. + Note the appending is done in-place and is not an atomic operation, so if something goes wrong + during the operation it's possible the archive could be left without a central directory (although the local + file headers and file data will be fine, so the archive will be recoverable). + + For more complex archive modification scenarios: + 1. The safest way is to use a mz_zip_reader to read the existing archive, cloning only those bits you want to + preserve into a new archive using using the mz_zip_writer_add_from_zip_reader() function (which compiles the + compressed file data as-is). When you're done, delete the old archive and rename the newly written archive, and + you're done. This is safe but requires a bunch of temporary disk space or heap memory. + + 2. Or, you can convert an mz_zip_reader in-place to an mz_zip_writer using mz_zip_writer_init_from_reader(), + append new files as needed, then finalize the archive which will write an updated central directory to the + original archive. (This is basically what mz_zip_add_mem_to_archive_file_in_place() does.) There's a + possibility that the archive's central directory could be lost with this method if anything goes wrong, though. + + - ZIP archive support limitations: + No zip64 or spanning support. Extraction functions can only handle unencrypted, stored or deflated files. + Requires streams capable of seeking. + + * This is a header file library, like stb_image.c. To get only a header file, either cut and paste the + below header, or create miniz.h, #define MINIZ_HEADER_FILE_ONLY, and then include miniz.c from it. + + * Important: For best perf. be sure to customize the below macros for your target platform: + #define MINIZ_USE_UNALIGNED_LOADS_AND_STORES 1 + #define MINIZ_LITTLE_ENDIAN 1 + #define MINIZ_HAS_64BIT_REGISTERS 1 + + * On platforms using glibc, Be sure to "#define _LARGEFILE64_SOURCE 1" before including miniz.c to ensure miniz + uses the 64-bit variants: fopen64(), stat64(), etc. Otherwise you won't be able to process large files + (i.e. 32-bit stat() fails for me on files > 0x7FFFFFFF bytes). +*/ + +#ifndef MINIZ_HEADER_INCLUDED +#define MINIZ_HEADER_INCLUDED + +#include + +// Defines to completely disable specific portions of miniz.c: +// If all macros here are defined the only functionality remaining will be CRC-32, adler-32, tinfl, and tdefl. + +// Define MINIZ_NO_STDIO to disable all usage and any functions which rely on stdio for file I/O. +//#define MINIZ_NO_STDIO + +// If MINIZ_NO_TIME is specified then the ZIP archive functions will not be able to get the current time, or +// get/set file times, and the C run-time funcs that get/set times won't be called. +// The current downside is the times written to your archives will be from 1979. +//#define MINIZ_NO_TIME + +// Define MINIZ_NO_ARCHIVE_APIS to disable all ZIP archive API's. +//#define MINIZ_NO_ARCHIVE_APIS + +// Define MINIZ_NO_ARCHIVE_APIS to disable all writing related ZIP archive API's. +//#define MINIZ_NO_ARCHIVE_WRITING_APIS + +// Define MINIZ_NO_ZLIB_APIS to remove all ZLIB-style compression/decompression API's. +//#define MINIZ_NO_ZLIB_APIS + +// Define MINIZ_NO_ZLIB_COMPATIBLE_NAME to disable zlib names, to prevent conflicts against stock zlib. +//#define MINIZ_NO_ZLIB_COMPATIBLE_NAMES + +// Define MINIZ_NO_MALLOC to disable all calls to malloc, free, and realloc. +// Note if MINIZ_NO_MALLOC is defined then the user must always provide custom user alloc/free/realloc +// callbacks to the zlib and archive API's, and a few stand-alone helper API's which don't provide custom user +// functions (such as tdefl_compress_mem_to_heap() and tinfl_decompress_mem_to_heap()) won't work. +//#define MINIZ_NO_MALLOC + +#if defined(__TINYC__) && (defined(__linux) || defined(__linux__)) + // TODO: Work around "error: include file 'sys\utime.h' when compiling with tcc on Linux + #define MINIZ_NO_TIME +#endif + +#if !defined(MINIZ_NO_TIME) && !defined(MINIZ_NO_ARCHIVE_APIS) + #include +#endif + +#if defined(_M_IX86) || defined(_M_X64) || defined(__i386__) || defined(__i386) || defined(__i486__) || defined(__i486) || defined(i386) || defined(__ia64__) || defined(__x86_64__) +// MINIZ_X86_OR_X64_CPU is only used to help set the below macros. +#define MINIZ_X86_OR_X64_CPU 1 +#endif + +#if (__BYTE_ORDER__==__ORDER_LITTLE_ENDIAN__) || MINIZ_X86_OR_X64_CPU +// Set MINIZ_LITTLE_ENDIAN to 1 if the processor is little endian. +#define MINIZ_LITTLE_ENDIAN 1 +#endif + +#if MINIZ_X86_OR_X64_CPU +// Set MINIZ_USE_UNALIGNED_LOADS_AND_STORES to 1 on CPU's that permit efficient integer loads and stores from unaligned addresses. +#define MINIZ_USE_UNALIGNED_LOADS_AND_STORES 1 +#endif + +#if defined(_M_X64) || defined(_WIN64) || defined(__MINGW64__) || defined(_LP64) || defined(__LP64__) || defined(__ia64__) || defined(__x86_64__) +// Set MINIZ_HAS_64BIT_REGISTERS to 1 if operations on 64-bit integers are reasonably fast (and don't involve compiler generated calls to helper functions). +#define MINIZ_HAS_64BIT_REGISTERS 1 +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +// ------------------- zlib-style API Definitions. + +// For more compatibility with zlib, miniz.c uses unsigned long for some parameters/struct members. Beware: mz_ulong can be either 32 or 64-bits! +typedef unsigned long mz_ulong; + +// mz_free() internally uses the MZ_FREE() macro (which by default calls free() unless you've modified the MZ_MALLOC macro) to release a block allocated from the heap. +void mz_free(void *p); + +#define MZ_ADLER32_INIT (1) +// mz_adler32() returns the initial adler-32 value to use when called with ptr==NULL. +mz_ulong mz_adler32(mz_ulong adler, const unsigned char *ptr, size_t buf_len); + +#define MZ_CRC32_INIT (0) +// mz_crc32() returns the initial CRC-32 value to use when called with ptr==NULL. +mz_ulong mz_crc32(mz_ulong crc, const unsigned char *ptr, size_t buf_len); + +// Compression strategies. +enum { MZ_DEFAULT_STRATEGY = 0, MZ_FILTERED = 1, MZ_HUFFMAN_ONLY = 2, MZ_RLE = 3, MZ_FIXED = 4 }; + +// Method +#define MZ_DEFLATED 8 + +#ifndef MINIZ_NO_ZLIB_APIS + +// Heap allocation callbacks. +// Note that mz_alloc_func parameter types purpsosely differ from zlib's: items/size is size_t, not unsigned long. +typedef void *(*mz_alloc_func)(void *opaque, size_t items, size_t size); +typedef void (*mz_free_func)(void *opaque, void *address); +typedef void *(*mz_realloc_func)(void *opaque, void *address, size_t items, size_t size); + +#define MZ_VERSION "9.1.15" +#define MZ_VERNUM 0x91F0 +#define MZ_VER_MAJOR 9 +#define MZ_VER_MINOR 1 +#define MZ_VER_REVISION 15 +#define MZ_VER_SUBREVISION 0 + +// Flush values. For typical usage you only need MZ_NO_FLUSH and MZ_FINISH. The other values are for advanced use (refer to the zlib docs). +enum { MZ_NO_FLUSH = 0, MZ_PARTIAL_FLUSH = 1, MZ_SYNC_FLUSH = 2, MZ_FULL_FLUSH = 3, MZ_FINISH = 4, MZ_BLOCK = 5 }; + +// Return status codes. MZ_PARAM_ERROR is non-standard. +enum { MZ_OK = 0, MZ_STREAM_END = 1, MZ_NEED_DICT = 2, MZ_ERRNO = -1, MZ_STREAM_ERROR = -2, MZ_DATA_ERROR = -3, MZ_MEM_ERROR = -4, MZ_BUF_ERROR = -5, MZ_VERSION_ERROR = -6, MZ_PARAM_ERROR = -10000 }; + +// Compression levels: 0-9 are the standard zlib-style levels, 10 is best possible compression (not zlib compatible, and may be very slow), MZ_DEFAULT_COMPRESSION=MZ_DEFAULT_LEVEL. +enum { MZ_NO_COMPRESSION = 0, MZ_BEST_SPEED = 1, MZ_BEST_COMPRESSION = 9, MZ_UBER_COMPRESSION = 10, MZ_DEFAULT_LEVEL = 6, MZ_DEFAULT_COMPRESSION = -1 }; + +// Window bits +#define MZ_DEFAULT_WINDOW_BITS 15 + +struct mz_internal_state; + +// Compression/decompression stream struct. +typedef struct mz_stream_s +{ + const unsigned char *next_in; // pointer to next byte to read + unsigned int avail_in; // number of bytes available at next_in + mz_ulong total_in; // total number of bytes consumed so far + + unsigned char *next_out; // pointer to next byte to write + unsigned int avail_out; // number of bytes that can be written to next_out + mz_ulong total_out; // total number of bytes produced so far + + char *msg; // error msg (unused) + struct mz_internal_state *state; // internal state, allocated by zalloc/zfree + + mz_alloc_func zalloc; // optional heap allocation function (defaults to malloc) + mz_free_func zfree; // optional heap free function (defaults to free) + void *opaque; // heap alloc function user pointer + + int data_type; // data_type (unused) + mz_ulong adler; // adler32 of the source or uncompressed data + mz_ulong reserved; // not used +} mz_stream; + +typedef mz_stream *mz_streamp; + +// Returns the version string of miniz.c. +const char *mz_version(void); + +// mz_deflateInit() initializes a compressor with default options: +// Parameters: +// pStream must point to an initialized mz_stream struct. +// level must be between [MZ_NO_COMPRESSION, MZ_BEST_COMPRESSION]. +// level 1 enables a specially optimized compression function that's been optimized purely for performance, not ratio. +// (This special func. is currently only enabled when MINIZ_USE_UNALIGNED_LOADS_AND_STORES and MINIZ_LITTLE_ENDIAN are defined.) +// Return values: +// MZ_OK on success. +// MZ_STREAM_ERROR if the stream is bogus. +// MZ_PARAM_ERROR if the input parameters are bogus. +// MZ_MEM_ERROR on out of memory. +int mz_deflateInit(mz_streamp pStream, int level); + +// mz_deflateInit2() is like mz_deflate(), except with more control: +// Additional parameters: +// method must be MZ_DEFLATED +// window_bits must be MZ_DEFAULT_WINDOW_BITS (to wrap the deflate stream with zlib header/adler-32 footer) or -MZ_DEFAULT_WINDOW_BITS (raw deflate/no header or footer) +// mem_level must be between [1, 9] (it's checked but ignored by miniz.c) +int mz_deflateInit2(mz_streamp pStream, int level, int method, int window_bits, int mem_level, int strategy); + +// Quickly resets a compressor without having to reallocate anything. Same as calling mz_deflateEnd() followed by mz_deflateInit()/mz_deflateInit2(). +int mz_deflateReset(mz_streamp pStream); + +// mz_deflate() compresses the input to output, consuming as much of the input and producing as much output as possible. +// Parameters: +// pStream is the stream to read from and write to. You must initialize/update the next_in, avail_in, next_out, and avail_out members. +// flush may be MZ_NO_FLUSH, MZ_PARTIAL_FLUSH/MZ_SYNC_FLUSH, MZ_FULL_FLUSH, or MZ_FINISH. +// Return values: +// MZ_OK on success (when flushing, or if more input is needed but not available, and/or there's more output to be written but the output buffer is full). +// MZ_STREAM_END if all input has been consumed and all output bytes have been written. Don't call mz_deflate() on the stream anymore. +// MZ_STREAM_ERROR if the stream is bogus. +// MZ_PARAM_ERROR if one of the parameters is invalid. +// MZ_BUF_ERROR if no forward progress is possible because the input and/or output buffers are empty. (Fill up the input buffer or free up some output space and try again.) +int mz_deflate(mz_streamp pStream, int flush); + +// mz_deflateEnd() deinitializes a compressor: +// Return values: +// MZ_OK on success. +// MZ_STREAM_ERROR if the stream is bogus. +int mz_deflateEnd(mz_streamp pStream); + +// mz_deflateBound() returns a (very) conservative upper bound on the amount of data that could be generated by deflate(), assuming flush is set to only MZ_NO_FLUSH or MZ_FINISH. +mz_ulong mz_deflateBound(mz_streamp pStream, mz_ulong source_len); + +// Single-call compression functions mz_compress() and mz_compress2(): +// Returns MZ_OK on success, or one of the error codes from mz_deflate() on failure. +int mz_compress(unsigned char *pDest, mz_ulong *pDest_len, const unsigned char *pSource, mz_ulong source_len); +int mz_compress2(unsigned char *pDest, mz_ulong *pDest_len, const unsigned char *pSource, mz_ulong source_len, int level); + +// mz_compressBound() returns a (very) conservative upper bound on the amount of data that could be generated by calling mz_compress(). +mz_ulong mz_compressBound(mz_ulong source_len); + +// Initializes a decompressor. +int mz_inflateInit(mz_streamp pStream); + +// mz_inflateInit2() is like mz_inflateInit() with an additional option that controls the window size and whether or not the stream has been wrapped with a zlib header/footer: +// window_bits must be MZ_DEFAULT_WINDOW_BITS (to parse zlib header/footer) or -MZ_DEFAULT_WINDOW_BITS (raw deflate). +int mz_inflateInit2(mz_streamp pStream, int window_bits); + +// Decompresses the input stream to the output, consuming only as much of the input as needed, and writing as much to the output as possible. +// Parameters: +// pStream is the stream to read from and write to. You must initialize/update the next_in, avail_in, next_out, and avail_out members. +// flush may be MZ_NO_FLUSH, MZ_SYNC_FLUSH, or MZ_FINISH. +// On the first call, if flush is MZ_FINISH it's assumed the input and output buffers are both sized large enough to decompress the entire stream in a single call (this is slightly faster). +// MZ_FINISH implies that there are no more source bytes available beside what's already in the input buffer, and that the output buffer is large enough to hold the rest of the decompressed data. +// Return values: +// MZ_OK on success. Either more input is needed but not available, and/or there's more output to be written but the output buffer is full. +// MZ_STREAM_END if all needed input has been consumed and all output bytes have been written. For zlib streams, the adler-32 of the decompressed data has also been verified. +// MZ_STREAM_ERROR if the stream is bogus. +// MZ_DATA_ERROR if the deflate stream is invalid. +// MZ_PARAM_ERROR if one of the parameters is invalid. +// MZ_BUF_ERROR if no forward progress is possible because the input buffer is empty but the inflater needs more input to continue, or if the output buffer is not large enough. Call mz_inflate() again +// with more input data, or with more room in the output buffer (except when using single call decompression, described above). +int mz_inflate(mz_streamp pStream, int flush); + +// Deinitializes a decompressor. +int mz_inflateEnd(mz_streamp pStream); + +// Single-call decompression. +// Returns MZ_OK on success, or one of the error codes from mz_inflate() on failure. +int mz_uncompress(unsigned char *pDest, mz_ulong *pDest_len, const unsigned char *pSource, mz_ulong source_len); + +// Returns a string description of the specified error code, or NULL if the error code is invalid. +const char *mz_error(int err); + +// Redefine zlib-compatible names to miniz equivalents, so miniz.c can be used as a drop-in replacement for the subset of zlib that miniz.c supports. +// Define MINIZ_NO_ZLIB_COMPATIBLE_NAMES to disable zlib-compatibility if you use zlib in the same project. +#ifndef MINIZ_NO_ZLIB_COMPATIBLE_NAMES + typedef unsigned char Byte; + typedef unsigned int uInt; + typedef mz_ulong uLong; + typedef Byte Bytef; + typedef uInt uIntf; + typedef char charf; + typedef int intf; + typedef void *voidpf; + typedef uLong uLongf; + typedef void *voidp; + typedef void *const voidpc; + #define Z_NULL 0 + #define Z_NO_FLUSH MZ_NO_FLUSH + #define Z_PARTIAL_FLUSH MZ_PARTIAL_FLUSH + #define Z_SYNC_FLUSH MZ_SYNC_FLUSH + #define Z_FULL_FLUSH MZ_FULL_FLUSH + #define Z_FINISH MZ_FINISH + #define Z_BLOCK MZ_BLOCK + #define Z_OK MZ_OK + #define Z_STREAM_END MZ_STREAM_END + #define Z_NEED_DICT MZ_NEED_DICT + #define Z_ERRNO MZ_ERRNO + #define Z_STREAM_ERROR MZ_STREAM_ERROR + #define Z_DATA_ERROR MZ_DATA_ERROR + #define Z_MEM_ERROR MZ_MEM_ERROR + #define Z_BUF_ERROR MZ_BUF_ERROR + #define Z_VERSION_ERROR MZ_VERSION_ERROR + #define Z_PARAM_ERROR MZ_PARAM_ERROR + #define Z_NO_COMPRESSION MZ_NO_COMPRESSION + #define Z_BEST_SPEED MZ_BEST_SPEED + #define Z_BEST_COMPRESSION MZ_BEST_COMPRESSION + #define Z_DEFAULT_COMPRESSION MZ_DEFAULT_COMPRESSION + #define Z_DEFAULT_STRATEGY MZ_DEFAULT_STRATEGY + #define Z_FILTERED MZ_FILTERED + #define Z_HUFFMAN_ONLY MZ_HUFFMAN_ONLY + #define Z_RLE MZ_RLE + #define Z_FIXED MZ_FIXED + #define Z_DEFLATED MZ_DEFLATED + #define Z_DEFAULT_WINDOW_BITS MZ_DEFAULT_WINDOW_BITS + #define alloc_func mz_alloc_func + #define free_func mz_free_func + #define internal_state mz_internal_state + #define z_stream mz_stream + #define deflateInit mz_deflateInit + #define deflateInit2 mz_deflateInit2 + #define deflateReset mz_deflateReset + #define deflate mz_deflate + #define deflateEnd mz_deflateEnd + #define deflateBound mz_deflateBound + #define compress mz_compress + #define compress2 mz_compress2 + #define compressBound mz_compressBound + #define inflateInit mz_inflateInit + #define inflateInit2 mz_inflateInit2 + #define inflate mz_inflate + #define inflateEnd mz_inflateEnd + #define uncompress mz_uncompress + #define crc32 mz_crc32 + #define adler32 mz_adler32 + #define MAX_WBITS 15 + #define MAX_MEM_LEVEL 9 + #define zError mz_error + #define ZLIB_VERSION MZ_VERSION + #define ZLIB_VERNUM MZ_VERNUM + #define ZLIB_VER_MAJOR MZ_VER_MAJOR + #define ZLIB_VER_MINOR MZ_VER_MINOR + #define ZLIB_VER_REVISION MZ_VER_REVISION + #define ZLIB_VER_SUBREVISION MZ_VER_SUBREVISION + #define zlibVersion mz_version + #define zlib_version mz_version() +#endif // #ifndef MINIZ_NO_ZLIB_COMPATIBLE_NAMES + +#endif // MINIZ_NO_ZLIB_APIS + +// ------------------- Types and macros + +typedef unsigned char mz_uint8; +typedef signed short mz_int16; +typedef unsigned short mz_uint16; +typedef unsigned int mz_uint32; +typedef unsigned int mz_uint; +typedef long long mz_int64; +typedef unsigned long long mz_uint64; +typedef int mz_bool; + +#define MZ_FALSE (0) +#define MZ_TRUE (1) + +// An attempt to work around MSVC's spammy "warning C4127: conditional expression is constant" message. +#ifdef _MSC_VER + #define MZ_MACRO_END while (0, 0) +#else + #define MZ_MACRO_END while (0) +#endif + +// ------------------- ZIP archive reading/writing + +#ifndef MINIZ_NO_ARCHIVE_APIS + +enum +{ + MZ_ZIP_MAX_IO_BUF_SIZE = 64*1024, + MZ_ZIP_MAX_ARCHIVE_FILENAME_SIZE = 260, + MZ_ZIP_MAX_ARCHIVE_FILE_COMMENT_SIZE = 256 +}; + +typedef struct +{ + mz_uint32 m_file_index; + mz_uint32 m_central_dir_ofs; + mz_uint16 m_version_made_by; + mz_uint16 m_version_needed; + mz_uint16 m_bit_flag; + mz_uint16 m_method; +#ifndef MINIZ_NO_TIME + time_t m_time; +#endif + mz_uint32 m_crc32; + mz_uint64 m_comp_size; + mz_uint64 m_uncomp_size; + mz_uint16 m_internal_attr; + mz_uint32 m_external_attr; + mz_uint64 m_local_header_ofs; + mz_uint32 m_comment_size; + char m_filename[MZ_ZIP_MAX_ARCHIVE_FILENAME_SIZE]; + char m_comment[MZ_ZIP_MAX_ARCHIVE_FILE_COMMENT_SIZE]; +} mz_zip_archive_file_stat; + +typedef size_t (*mz_file_read_func)(void *pOpaque, mz_uint64 file_ofs, void *pBuf, size_t n); +typedef size_t (*mz_file_write_func)(void *pOpaque, mz_uint64 file_ofs, const void *pBuf, size_t n); + +struct mz_zip_internal_state_tag; +typedef struct mz_zip_internal_state_tag mz_zip_internal_state; + +typedef enum +{ + MZ_ZIP_MODE_INVALID = 0, + MZ_ZIP_MODE_READING = 1, + MZ_ZIP_MODE_WRITING = 2, + MZ_ZIP_MODE_WRITING_HAS_BEEN_FINALIZED = 3 +} mz_zip_mode; + +typedef struct mz_zip_archive_tag +{ + mz_uint64 m_archive_size; + mz_uint64 m_central_directory_file_ofs; + mz_uint m_total_files; + mz_zip_mode m_zip_mode; + + mz_uint m_file_offset_alignment; + + mz_alloc_func m_pAlloc; + mz_free_func m_pFree; + mz_realloc_func m_pRealloc; + void *m_pAlloc_opaque; + + mz_file_read_func m_pRead; + mz_file_write_func m_pWrite; + void *m_pIO_opaque; + + mz_zip_internal_state *m_pState; + +} mz_zip_archive; + +typedef enum +{ + MZ_ZIP_FLAG_CASE_SENSITIVE = 0x0100, + MZ_ZIP_FLAG_IGNORE_PATH = 0x0200, + MZ_ZIP_FLAG_COMPRESSED_DATA = 0x0400, + MZ_ZIP_FLAG_DO_NOT_SORT_CENTRAL_DIRECTORY = 0x0800 +} mz_zip_flags; + +// ZIP archive reading + +// Inits a ZIP archive reader. +// These functions read and validate the archive's central directory. +mz_bool mz_zip_reader_init(mz_zip_archive *pZip, mz_uint64 size, mz_uint32 flags); +mz_bool mz_zip_reader_init_mem(mz_zip_archive *pZip, const void *pMem, size_t size, mz_uint32 flags); + +#ifndef MINIZ_NO_STDIO +mz_bool mz_zip_reader_init_file(mz_zip_archive *pZip, const char *pFilename, mz_uint32 flags); +#endif + +// Returns the total number of files in the archive. +mz_uint mz_zip_reader_get_num_files(mz_zip_archive *pZip); + +// Returns detailed information about an archive file entry. +mz_bool mz_zip_reader_file_stat(mz_zip_archive *pZip, mz_uint file_index, mz_zip_archive_file_stat *pStat); + +// Determines if an archive file entry is a directory entry. +mz_bool mz_zip_reader_is_file_a_directory(mz_zip_archive *pZip, mz_uint file_index); +mz_bool mz_zip_reader_is_file_encrypted(mz_zip_archive *pZip, mz_uint file_index); + +// Retrieves the filename of an archive file entry. +// Returns the number of bytes written to pFilename, or if filename_buf_size is 0 this function returns the number of bytes needed to fully store the filename. +mz_uint mz_zip_reader_get_filename(mz_zip_archive *pZip, mz_uint file_index, char *pFilename, mz_uint filename_buf_size); + +// Attempts to locates a file in the archive's central directory. +// Valid flags: MZ_ZIP_FLAG_CASE_SENSITIVE, MZ_ZIP_FLAG_IGNORE_PATH +// Returns -1 if the file cannot be found. +int mz_zip_reader_locate_file(mz_zip_archive *pZip, const char *pName, const char *pComment, mz_uint flags); + +// Extracts a archive file to a memory buffer using no memory allocation. +mz_bool mz_zip_reader_extract_to_mem_no_alloc(mz_zip_archive *pZip, mz_uint file_index, void *pBuf, size_t buf_size, mz_uint flags, void *pUser_read_buf, size_t user_read_buf_size); +mz_bool mz_zip_reader_extract_file_to_mem_no_alloc(mz_zip_archive *pZip, const char *pFilename, void *pBuf, size_t buf_size, mz_uint flags, void *pUser_read_buf, size_t user_read_buf_size); + +// Extracts a archive file to a memory buffer. +mz_bool mz_zip_reader_extract_to_mem(mz_zip_archive *pZip, mz_uint file_index, void *pBuf, size_t buf_size, mz_uint flags); +mz_bool mz_zip_reader_extract_file_to_mem(mz_zip_archive *pZip, const char *pFilename, void *pBuf, size_t buf_size, mz_uint flags); + +// Extracts a archive file to a dynamically allocated heap buffer. +void *mz_zip_reader_extract_to_heap(mz_zip_archive *pZip, mz_uint file_index, size_t *pSize, mz_uint flags); +void *mz_zip_reader_extract_file_to_heap(mz_zip_archive *pZip, const char *pFilename, size_t *pSize, mz_uint flags); + +// Extracts a archive file using a callback function to output the file's data. +mz_bool mz_zip_reader_extract_to_callback(mz_zip_archive *pZip, mz_uint file_index, mz_file_write_func pCallback, void *pOpaque, mz_uint flags); +mz_bool mz_zip_reader_extract_file_to_callback(mz_zip_archive *pZip, const char *pFilename, mz_file_write_func pCallback, void *pOpaque, mz_uint flags); + +#ifndef MINIZ_NO_STDIO +// Extracts a archive file to a disk file and sets its last accessed and modified times. +// This function only extracts files, not archive directory records. +mz_bool mz_zip_reader_extract_to_file(mz_zip_archive *pZip, mz_uint file_index, const char *pDst_filename, mz_uint flags); +mz_bool mz_zip_reader_extract_file_to_file(mz_zip_archive *pZip, const char *pArchive_filename, const char *pDst_filename, mz_uint flags); +#endif + +// Ends archive reading, freeing all allocations, and closing the input archive file if mz_zip_reader_init_file() was used. +mz_bool mz_zip_reader_end(mz_zip_archive *pZip); + +// ZIP archive writing + +#ifndef MINIZ_NO_ARCHIVE_WRITING_APIS + +// Inits a ZIP archive writer. +mz_bool mz_zip_writer_init(mz_zip_archive *pZip, mz_uint64 existing_size); +mz_bool mz_zip_writer_init_heap(mz_zip_archive *pZip, size_t size_to_reserve_at_beginning, size_t initial_allocation_size); + +#ifndef MINIZ_NO_STDIO +mz_bool mz_zip_writer_init_file(mz_zip_archive *pZip, const char *pFilename, mz_uint64 size_to_reserve_at_beginning); +#endif + +// Converts a ZIP archive reader object into a writer object, to allow efficient in-place file appends to occur on an existing archive. +// For archives opened using mz_zip_reader_init_file, pFilename must be the archive's filename so it can be reopened for writing. If the file can't be reopened, mz_zip_reader_end() will be called. +// For archives opened using mz_zip_reader_init_mem, the memory block must be growable using the realloc callback (which defaults to realloc unless you've overridden it). +// Finally, for archives opened using mz_zip_reader_init, the mz_zip_archive's user provided m_pWrite function cannot be NULL. +// Note: In-place archive modification is not recommended unless you know what you're doing, because if execution stops or something goes wrong before +// the archive is finalized the file's central directory will be hosed. +mz_bool mz_zip_writer_init_from_reader(mz_zip_archive *pZip, const char *pFilename); + +// Adds the contents of a memory buffer to an archive. These functions record the current local time into the archive. +// To add a directory entry, call this method with an archive name ending in a forwardslash with empty buffer. +// level_and_flags - compression level (0-10, see MZ_BEST_SPEED, MZ_BEST_COMPRESSION, etc.) logically OR'd with zero or more mz_zip_flags, or just set to MZ_DEFAULT_COMPRESSION. +mz_bool mz_zip_writer_add_mem(mz_zip_archive *pZip, const char *pArchive_name, const void *pBuf, size_t buf_size, mz_uint level_and_flags); +mz_bool mz_zip_writer_add_mem_ex(mz_zip_archive *pZip, const char *pArchive_name, const void *pBuf, size_t buf_size, const void *pComment, mz_uint16 comment_size, mz_uint level_and_flags, mz_uint64 uncomp_size, mz_uint32 uncomp_crc32); + +#ifndef MINIZ_NO_STDIO +// Adds the contents of a disk file to an archive. This function also records the disk file's modified time into the archive. +// level_and_flags - compression level (0-10, see MZ_BEST_SPEED, MZ_BEST_COMPRESSION, etc.) logically OR'd with zero or more mz_zip_flags, or just set to MZ_DEFAULT_COMPRESSION. +mz_bool mz_zip_writer_add_file(mz_zip_archive *pZip, const char *pArchive_name, const char *pSrc_filename, const void *pComment, mz_uint16 comment_size, mz_uint level_and_flags); +#endif + +// Adds a file to an archive by fully cloning the data from another archive. +// This function fully clones the source file's compressed data (no recompression), along with its full filename, extra data, and comment fields. +mz_bool mz_zip_writer_add_from_zip_reader(mz_zip_archive *pZip, mz_zip_archive *pSource_zip, mz_uint file_index); + +// Finalizes the archive by writing the central directory records followed by the end of central directory record. +// After an archive is finalized, the only valid call on the mz_zip_archive struct is mz_zip_writer_end(). +// An archive must be manually finalized by calling this function for it to be valid. +mz_bool mz_zip_writer_finalize_archive(mz_zip_archive *pZip); +mz_bool mz_zip_writer_finalize_heap_archive(mz_zip_archive *pZip, void **pBuf, size_t *pSize); + +// Ends archive writing, freeing all allocations, and closing the output file if mz_zip_writer_init_file() was used. +// Note for the archive to be valid, it must have been finalized before ending. +mz_bool mz_zip_writer_end(mz_zip_archive *pZip); + +// Misc. high-level helper functions: + +// mz_zip_add_mem_to_archive_file_in_place() efficiently (but not atomically) appends a memory blob to a ZIP archive. +// level_and_flags - compression level (0-10, see MZ_BEST_SPEED, MZ_BEST_COMPRESSION, etc.) logically OR'd with zero or more mz_zip_flags, or just set to MZ_DEFAULT_COMPRESSION. +mz_bool mz_zip_add_mem_to_archive_file_in_place(const char *pZip_filename, const char *pArchive_name, const void *pBuf, size_t buf_size, const void *pComment, mz_uint16 comment_size, mz_uint level_and_flags); + +// Reads a single file from an archive into a heap block. +// Returns NULL on failure. +void *mz_zip_extract_archive_file_to_heap(const char *pZip_filename, const char *pArchive_name, size_t *pSize, mz_uint zip_flags); + +#endif // #ifndef MINIZ_NO_ARCHIVE_WRITING_APIS + +#endif // #ifndef MINIZ_NO_ARCHIVE_APIS + +// ------------------- Low-level Decompression API Definitions + +// Decompression flags used by tinfl_decompress(). +// TINFL_FLAG_PARSE_ZLIB_HEADER: If set, the input has a valid zlib header and ends with an adler32 checksum (it's a valid zlib stream). Otherwise, the input is a raw deflate stream. +// TINFL_FLAG_HAS_MORE_INPUT: If set, there are more input bytes available beyond the end of the supplied input buffer. If clear, the input buffer contains all remaining input. +// TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF: If set, the output buffer is large enough to hold the entire decompressed stream. If clear, the output buffer is at least the size of the dictionary (typically 32KB). +// TINFL_FLAG_COMPUTE_ADLER32: Force adler-32 checksum computation of the decompressed bytes. +enum +{ + TINFL_FLAG_PARSE_ZLIB_HEADER = 1, + TINFL_FLAG_HAS_MORE_INPUT = 2, + TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF = 4, + TINFL_FLAG_COMPUTE_ADLER32 = 8 +}; + +// High level decompression functions: +// tinfl_decompress_mem_to_heap() decompresses a block in memory to a heap block allocated via malloc(). +// On entry: +// pSrc_buf, src_buf_len: Pointer and size of the Deflate or zlib source data to decompress. +// On return: +// Function returns a pointer to the decompressed data, or NULL on failure. +// *pOut_len will be set to the decompressed data's size, which could be larger than src_buf_len on uncompressible data. +// The caller must call mz_free() on the returned block when it's no longer needed. +void *tinfl_decompress_mem_to_heap(const void *pSrc_buf, size_t src_buf_len, size_t *pOut_len, int flags); + +// tinfl_decompress_mem_to_mem() decompresses a block in memory to another block in memory. +// Returns TINFL_DECOMPRESS_MEM_TO_MEM_FAILED on failure, or the number of bytes written on success. +#define TINFL_DECOMPRESS_MEM_TO_MEM_FAILED ((size_t)(-1)) +size_t tinfl_decompress_mem_to_mem(void *pOut_buf, size_t out_buf_len, const void *pSrc_buf, size_t src_buf_len, int flags); + +// tinfl_decompress_mem_to_callback() decompresses a block in memory to an internal 32KB buffer, and a user provided callback function will be called to flush the buffer. +// Returns 1 on success or 0 on failure. +typedef int (*tinfl_put_buf_func_ptr)(const void* pBuf, int len, void *pUser); +int tinfl_decompress_mem_to_callback(const void *pIn_buf, size_t *pIn_buf_size, tinfl_put_buf_func_ptr pPut_buf_func, void *pPut_buf_user, int flags); + +struct tinfl_decompressor_tag; typedef struct tinfl_decompressor_tag tinfl_decompressor; + +// Max size of LZ dictionary. +#define TINFL_LZ_DICT_SIZE 32768 + +// Return status. +typedef enum +{ + TINFL_STATUS_BAD_PARAM = -3, + TINFL_STATUS_ADLER32_MISMATCH = -2, + TINFL_STATUS_FAILED = -1, + TINFL_STATUS_DONE = 0, + TINFL_STATUS_NEEDS_MORE_INPUT = 1, + TINFL_STATUS_HAS_MORE_OUTPUT = 2 +} tinfl_status; + +// Initializes the decompressor to its initial state. +#define tinfl_init(r) do { (r)->m_state = 0; } MZ_MACRO_END +#define tinfl_get_adler32(r) (r)->m_check_adler32 + +// Main low-level decompressor coroutine function. This is the only function actually needed for decompression. All the other functions are just high-level helpers for improved usability. +// This is a universal API, i.e. it can be used as a building block to build any desired higher level decompression API. In the limit case, it can be called once per every byte input or output. +tinfl_status tinfl_decompress(tinfl_decompressor *r, const mz_uint8 *pIn_buf_next, size_t *pIn_buf_size, mz_uint8 *pOut_buf_start, mz_uint8 *pOut_buf_next, size_t *pOut_buf_size, const mz_uint32 decomp_flags); + +// Internal/private bits follow. +enum +{ + TINFL_MAX_HUFF_TABLES = 3, TINFL_MAX_HUFF_SYMBOLS_0 = 288, TINFL_MAX_HUFF_SYMBOLS_1 = 32, TINFL_MAX_HUFF_SYMBOLS_2 = 19, + TINFL_FAST_LOOKUP_BITS = 10, TINFL_FAST_LOOKUP_SIZE = 1 << TINFL_FAST_LOOKUP_BITS +}; + +typedef struct +{ + mz_uint8 m_code_size[TINFL_MAX_HUFF_SYMBOLS_0]; + mz_int16 m_look_up[TINFL_FAST_LOOKUP_SIZE], m_tree[TINFL_MAX_HUFF_SYMBOLS_0 * 2]; +} tinfl_huff_table; + +#if MINIZ_HAS_64BIT_REGISTERS + #define TINFL_USE_64BIT_BITBUF 1 +#endif + +#if TINFL_USE_64BIT_BITBUF + typedef mz_uint64 tinfl_bit_buf_t; + #define TINFL_BITBUF_SIZE (64) +#else + typedef mz_uint32 tinfl_bit_buf_t; + #define TINFL_BITBUF_SIZE (32) +#endif + +struct tinfl_decompressor_tag +{ + mz_uint32 m_state, m_num_bits, m_zhdr0, m_zhdr1, m_z_adler32, m_final, m_type, m_check_adler32, m_dist, m_counter, m_num_extra, m_table_sizes[TINFL_MAX_HUFF_TABLES]; + tinfl_bit_buf_t m_bit_buf; + size_t m_dist_from_out_buf_start; + tinfl_huff_table m_tables[TINFL_MAX_HUFF_TABLES]; + mz_uint8 m_raw_header[4], m_len_codes[TINFL_MAX_HUFF_SYMBOLS_0 + TINFL_MAX_HUFF_SYMBOLS_1 + 137]; +}; + +// ------------------- Low-level Compression API Definitions + +// Set TDEFL_LESS_MEMORY to 1 to use less memory (compression will be slightly slower, and raw/dynamic blocks will be output more frequently). +#define TDEFL_LESS_MEMORY 0 + +// tdefl_init() compression flags logically OR'd together (low 12 bits contain the max. number of probes per dictionary search): +// TDEFL_DEFAULT_MAX_PROBES: The compressor defaults to 128 dictionary probes per dictionary search. 0=Huffman only, 1=Huffman+LZ (fastest/crap compression), 4095=Huffman+LZ (slowest/best compression). +enum +{ + TDEFL_HUFFMAN_ONLY = 0, TDEFL_DEFAULT_MAX_PROBES = 128, TDEFL_MAX_PROBES_MASK = 0xFFF +}; + +// TDEFL_WRITE_ZLIB_HEADER: If set, the compressor outputs a zlib header before the deflate data, and the Adler-32 of the source data at the end. Otherwise, you'll get raw deflate data. +// TDEFL_COMPUTE_ADLER32: Always compute the adler-32 of the input data (even when not writing zlib headers). +// TDEFL_GREEDY_PARSING_FLAG: Set to use faster greedy parsing, instead of more efficient lazy parsing. +// TDEFL_NONDETERMINISTIC_PARSING_FLAG: Enable to decrease the compressor's initialization time to the minimum, but the output may vary from run to run given the same input (depending on the contents of memory). +// TDEFL_RLE_MATCHES: Only look for RLE matches (matches with a distance of 1) +// TDEFL_FILTER_MATCHES: Discards matches <= 5 chars if enabled. +// TDEFL_FORCE_ALL_STATIC_BLOCKS: Disable usage of optimized Huffman tables. +// TDEFL_FORCE_ALL_RAW_BLOCKS: Only use raw (uncompressed) deflate blocks. +// The low 12 bits are reserved to control the max # of hash probes per dictionary lookup (see TDEFL_MAX_PROBES_MASK). +enum +{ + TDEFL_WRITE_ZLIB_HEADER = 0x01000, + TDEFL_COMPUTE_ADLER32 = 0x02000, + TDEFL_GREEDY_PARSING_FLAG = 0x04000, + TDEFL_NONDETERMINISTIC_PARSING_FLAG = 0x08000, + TDEFL_RLE_MATCHES = 0x10000, + TDEFL_FILTER_MATCHES = 0x20000, + TDEFL_FORCE_ALL_STATIC_BLOCKS = 0x40000, + TDEFL_FORCE_ALL_RAW_BLOCKS = 0x80000 +}; + +// High level compression functions: +// tdefl_compress_mem_to_heap() compresses a block in memory to a heap block allocated via malloc(). +// On entry: +// pSrc_buf, src_buf_len: Pointer and size of source block to compress. +// flags: The max match finder probes (default is 128) logically OR'd against the above flags. Higher probes are slower but improve compression. +// On return: +// Function returns a pointer to the compressed data, or NULL on failure. +// *pOut_len will be set to the compressed data's size, which could be larger than src_buf_len on uncompressible data. +// The caller must free() the returned block when it's no longer needed. +void *tdefl_compress_mem_to_heap(const void *pSrc_buf, size_t src_buf_len, size_t *pOut_len, int flags); + +// tdefl_compress_mem_to_mem() compresses a block in memory to another block in memory. +// Returns 0 on failure. +size_t tdefl_compress_mem_to_mem(void *pOut_buf, size_t out_buf_len, const void *pSrc_buf, size_t src_buf_len, int flags); + +// Compresses an image to a compressed PNG file in memory. +// On entry: +// pImage, w, h, and num_chans describe the image to compress. num_chans may be 1, 2, 3, or 4. +// The image pitch in bytes per scanline will be w*num_chans. The leftmost pixel on the top scanline is stored first in memory. +// level may range from [0,10], use MZ_NO_COMPRESSION, MZ_BEST_SPEED, MZ_BEST_COMPRESSION, etc. or a decent default is MZ_DEFAULT_LEVEL +// If flip is true, the image will be flipped on the Y axis (useful for OpenGL apps). +// On return: +// Function returns a pointer to the compressed data, or NULL on failure. +// *pLen_out will be set to the size of the PNG image file. +// The caller must mz_free() the returned heap block (which will typically be larger than *pLen_out) when it's no longer needed. +void *tdefl_write_image_to_png_file_in_memory_ex(const void *pImage, int w, int h, int num_chans, size_t *pLen_out, mz_uint level, mz_bool flip); +void *tdefl_write_image_to_png_file_in_memory(const void *pImage, int w, int h, int num_chans, size_t *pLen_out); + +// Output stream interface. The compressor uses this interface to write compressed data. It'll typically be called TDEFL_OUT_BUF_SIZE at a time. +typedef mz_bool (*tdefl_put_buf_func_ptr)(const void* pBuf, int len, void *pUser); + +// tdefl_compress_mem_to_output() compresses a block to an output stream. The above helpers use this function internally. +mz_bool tdefl_compress_mem_to_output(const void *pBuf, size_t buf_len, tdefl_put_buf_func_ptr pPut_buf_func, void *pPut_buf_user, int flags); + +enum { TDEFL_MAX_HUFF_TABLES = 3, TDEFL_MAX_HUFF_SYMBOLS_0 = 288, TDEFL_MAX_HUFF_SYMBOLS_1 = 32, TDEFL_MAX_HUFF_SYMBOLS_2 = 19, TDEFL_LZ_DICT_SIZE = 32768, TDEFL_LZ_DICT_SIZE_MASK = TDEFL_LZ_DICT_SIZE - 1, TDEFL_MIN_MATCH_LEN = 3, TDEFL_MAX_MATCH_LEN = 258 }; + +// TDEFL_OUT_BUF_SIZE MUST be large enough to hold a single entire compressed output block (using static/fixed Huffman codes). +#if TDEFL_LESS_MEMORY +enum { TDEFL_LZ_CODE_BUF_SIZE = 24 * 1024, TDEFL_OUT_BUF_SIZE = (TDEFL_LZ_CODE_BUF_SIZE * 13 ) / 10, TDEFL_MAX_HUFF_SYMBOLS = 288, TDEFL_LZ_HASH_BITS = 12, TDEFL_LEVEL1_HASH_SIZE_MASK = 4095, TDEFL_LZ_HASH_SHIFT = (TDEFL_LZ_HASH_BITS + 2) / 3, TDEFL_LZ_HASH_SIZE = 1 << TDEFL_LZ_HASH_BITS }; +#else +enum { TDEFL_LZ_CODE_BUF_SIZE = 64 * 1024, TDEFL_OUT_BUF_SIZE = (TDEFL_LZ_CODE_BUF_SIZE * 13 ) / 10, TDEFL_MAX_HUFF_SYMBOLS = 288, TDEFL_LZ_HASH_BITS = 15, TDEFL_LEVEL1_HASH_SIZE_MASK = 4095, TDEFL_LZ_HASH_SHIFT = (TDEFL_LZ_HASH_BITS + 2) / 3, TDEFL_LZ_HASH_SIZE = 1 << TDEFL_LZ_HASH_BITS }; +#endif + +// The low-level tdefl functions below may be used directly if the above helper functions aren't flexible enough. The low-level functions don't make any heap allocations, unlike the above helper functions. +typedef enum +{ + TDEFL_STATUS_BAD_PARAM = -2, + TDEFL_STATUS_PUT_BUF_FAILED = -1, + TDEFL_STATUS_OKAY = 0, + TDEFL_STATUS_DONE = 1, +} tdefl_status; + +// Must map to MZ_NO_FLUSH, MZ_SYNC_FLUSH, etc. enums +typedef enum +{ + TDEFL_NO_FLUSH = 0, + TDEFL_SYNC_FLUSH = 2, + TDEFL_FULL_FLUSH = 3, + TDEFL_FINISH = 4 +} tdefl_flush; + +// tdefl's compression state structure. +typedef struct +{ + tdefl_put_buf_func_ptr m_pPut_buf_func; + void *m_pPut_buf_user; + mz_uint m_flags, m_max_probes[2]; + int m_greedy_parsing; + mz_uint m_adler32, m_lookahead_pos, m_lookahead_size, m_dict_size; + mz_uint8 *m_pLZ_code_buf, *m_pLZ_flags, *m_pOutput_buf, *m_pOutput_buf_end; + mz_uint m_num_flags_left, m_total_lz_bytes, m_lz_code_buf_dict_pos, m_bits_in, m_bit_buffer; + mz_uint m_saved_match_dist, m_saved_match_len, m_saved_lit, m_output_flush_ofs, m_output_flush_remaining, m_finished, m_block_index, m_wants_to_finish; + tdefl_status m_prev_return_status; + const void *m_pIn_buf; + void *m_pOut_buf; + size_t *m_pIn_buf_size, *m_pOut_buf_size; + tdefl_flush m_flush; + const mz_uint8 *m_pSrc; + size_t m_src_buf_left, m_out_buf_ofs; + mz_uint8 m_dict[TDEFL_LZ_DICT_SIZE + TDEFL_MAX_MATCH_LEN - 1]; + mz_uint16 m_huff_count[TDEFL_MAX_HUFF_TABLES][TDEFL_MAX_HUFF_SYMBOLS]; + mz_uint16 m_huff_codes[TDEFL_MAX_HUFF_TABLES][TDEFL_MAX_HUFF_SYMBOLS]; + mz_uint8 m_huff_code_sizes[TDEFL_MAX_HUFF_TABLES][TDEFL_MAX_HUFF_SYMBOLS]; + mz_uint8 m_lz_code_buf[TDEFL_LZ_CODE_BUF_SIZE]; + mz_uint16 m_next[TDEFL_LZ_DICT_SIZE]; + mz_uint16 m_hash[TDEFL_LZ_HASH_SIZE]; + mz_uint8 m_output_buf[TDEFL_OUT_BUF_SIZE]; +} tdefl_compressor; + +// Initializes the compressor. +// There is no corresponding deinit() function because the tdefl API's do not dynamically allocate memory. +// pBut_buf_func: If NULL, output data will be supplied to the specified callback. In this case, the user should call the tdefl_compress_buffer() API for compression. +// If pBut_buf_func is NULL the user should always call the tdefl_compress() API. +// flags: See the above enums (TDEFL_HUFFMAN_ONLY, TDEFL_WRITE_ZLIB_HEADER, etc.) +tdefl_status tdefl_init(tdefl_compressor *d, tdefl_put_buf_func_ptr pPut_buf_func, void *pPut_buf_user, int flags); + +// Compresses a block of data, consuming as much of the specified input buffer as possible, and writing as much compressed data to the specified output buffer as possible. +tdefl_status tdefl_compress(tdefl_compressor *d, const void *pIn_buf, size_t *pIn_buf_size, void *pOut_buf, size_t *pOut_buf_size, tdefl_flush flush); + +// tdefl_compress_buffer() is only usable when the tdefl_init() is called with a non-NULL tdefl_put_buf_func_ptr. +// tdefl_compress_buffer() always consumes the entire input buffer. +tdefl_status tdefl_compress_buffer(tdefl_compressor *d, const void *pIn_buf, size_t in_buf_size, tdefl_flush flush); + +tdefl_status tdefl_get_prev_return_status(tdefl_compressor *d); +mz_uint32 tdefl_get_adler32(tdefl_compressor *d); + +// Can't use tdefl_create_comp_flags_from_zip_params if MINIZ_NO_ZLIB_APIS isn't defined, because it uses some of its macros. +#ifndef MINIZ_NO_ZLIB_APIS +// Create tdefl_compress() flags given zlib-style compression parameters. +// level may range from [0,10] (where 10 is absolute max compression, but may be much slower on some files) +// window_bits may be -15 (raw deflate) or 15 (zlib) +// strategy may be either MZ_DEFAULT_STRATEGY, MZ_FILTERED, MZ_HUFFMAN_ONLY, MZ_RLE, or MZ_FIXED +mz_uint tdefl_create_comp_flags_from_zip_params(int level, int window_bits, int strategy); +#endif // #ifndef MINIZ_NO_ZLIB_APIS + +#ifdef __cplusplus +} +#endif + +#endif // MINIZ_HEADER_INCLUDED + +// ------------------- End of Header: Implementation follows. (If you only want the header, define MINIZ_HEADER_FILE_ONLY.) + +#ifndef MINIZ_HEADER_FILE_ONLY + +typedef unsigned char mz_validate_uint16[sizeof(mz_uint16)==2 ? 1 : -1]; +typedef unsigned char mz_validate_uint32[sizeof(mz_uint32)==4 ? 1 : -1]; +typedef unsigned char mz_validate_uint64[sizeof(mz_uint64)==8 ? 1 : -1]; + +#include +#include + +#define MZ_ASSERT(x) assert(x) + +#ifdef MINIZ_NO_MALLOC + #define MZ_MALLOC(x) NULL + #define MZ_FREE(x) (void)x, ((void)0) + #define MZ_REALLOC(p, x) NULL +#else + #define MZ_MALLOC(x) malloc(x) + #define MZ_FREE(x) free(x) + #define MZ_REALLOC(p, x) realloc(p, x) +#endif + +#define MZ_MAX(a,b) (((a)>(b))?(a):(b)) +#define MZ_MIN(a,b) (((a)<(b))?(a):(b)) +#define MZ_CLEAR_OBJ(obj) memset(&(obj), 0, sizeof(obj)) + +#if MINIZ_USE_UNALIGNED_LOADS_AND_STORES && MINIZ_LITTLE_ENDIAN + #define MZ_READ_LE16(p) *((const mz_uint16 *)(p)) + #define MZ_READ_LE32(p) *((const mz_uint32 *)(p)) +#else + #define MZ_READ_LE16(p) ((mz_uint32)(((const mz_uint8 *)(p))[0]) | ((mz_uint32)(((const mz_uint8 *)(p))[1]) << 8U)) + #define MZ_READ_LE32(p) ((mz_uint32)(((const mz_uint8 *)(p))[0]) | ((mz_uint32)(((const mz_uint8 *)(p))[1]) << 8U) | ((mz_uint32)(((const mz_uint8 *)(p))[2]) << 16U) | ((mz_uint32)(((const mz_uint8 *)(p))[3]) << 24U)) +#endif + +#ifdef _MSC_VER + #define MZ_FORCEINLINE __forceinline +#elif defined(__GNUC__) + #define MZ_FORCEINLINE inline __attribute__((__always_inline__)) +#else + #define MZ_FORCEINLINE inline +#endif + +#ifdef __cplusplus + extern "C" { +#endif + +// ------------------- zlib-style API's + +mz_ulong mz_adler32(mz_ulong adler, const unsigned char *ptr, size_t buf_len) +{ + mz_uint32 i, s1 = (mz_uint32)(adler & 0xffff), s2 = (mz_uint32)(adler >> 16); size_t block_len = buf_len % 5552; + if (!ptr) return MZ_ADLER32_INIT; + while (buf_len) { + for (i = 0; i + 7 < block_len; i += 8, ptr += 8) { + s1 += ptr[0], s2 += s1; s1 += ptr[1], s2 += s1; s1 += ptr[2], s2 += s1; s1 += ptr[3], s2 += s1; + s1 += ptr[4], s2 += s1; s1 += ptr[5], s2 += s1; s1 += ptr[6], s2 += s1; s1 += ptr[7], s2 += s1; + } + for ( ; i < block_len; ++i) s1 += *ptr++, s2 += s1; + s1 %= 65521U, s2 %= 65521U; buf_len -= block_len; block_len = 5552; + } + return (s2 << 16) + s1; +} + +// Karl Malbrain's compact CRC-32. See "A compact CCITT crc16 and crc32 C implementation that balances processor cache usage against speed": http://www.geocities.com/malbrain/ +mz_ulong mz_crc32(mz_ulong crc, const mz_uint8 *ptr, size_t buf_len) +{ + static const mz_uint32 s_crc32[16] = { 0, 0x1db71064, 0x3b6e20c8, 0x26d930ac, 0x76dc4190, 0x6b6b51f4, 0x4db26158, 0x5005713c, + 0xedb88320, 0xf00f9344, 0xd6d6a3e8, 0xcb61b38c, 0x9b64c2b0, 0x86d3d2d4, 0xa00ae278, 0xbdbdf21c }; + mz_uint32 crcu32 = (mz_uint32)crc; + if (!ptr) return MZ_CRC32_INIT; + crcu32 = ~crcu32; while (buf_len--) { mz_uint8 b = *ptr++; crcu32 = (crcu32 >> 4) ^ s_crc32[(crcu32 & 0xF) ^ (b & 0xF)]; crcu32 = (crcu32 >> 4) ^ s_crc32[(crcu32 & 0xF) ^ (b >> 4)]; } + return ~crcu32; +} + +void mz_free(void *p) +{ + MZ_FREE(p); +} + +#ifndef MINIZ_NO_ZLIB_APIS + +static void *def_alloc_func(void *opaque, size_t items, size_t size) { (void)opaque, (void)items, (void)size; return MZ_MALLOC(items * size); } +static void def_free_func(void *opaque, void *address) { (void)opaque, (void)address; MZ_FREE(address); } +static void *def_realloc_func(void *opaque, void *address, size_t items, size_t size) { (void)opaque, (void)address, (void)items, (void)size; return MZ_REALLOC(address, items * size); } + +const char *mz_version(void) +{ + return MZ_VERSION; +} + +int mz_deflateInit(mz_streamp pStream, int level) +{ + return mz_deflateInit2(pStream, level, MZ_DEFLATED, MZ_DEFAULT_WINDOW_BITS, 9, MZ_DEFAULT_STRATEGY); +} + +int mz_deflateInit2(mz_streamp pStream, int level, int method, int window_bits, int mem_level, int strategy) +{ + tdefl_compressor *pComp; + mz_uint comp_flags = TDEFL_COMPUTE_ADLER32 | tdefl_create_comp_flags_from_zip_params(level, window_bits, strategy); + + if (!pStream) return MZ_STREAM_ERROR; + if ((method != MZ_DEFLATED) || ((mem_level < 1) || (mem_level > 9)) || ((window_bits != MZ_DEFAULT_WINDOW_BITS) && (-window_bits != MZ_DEFAULT_WINDOW_BITS))) return MZ_PARAM_ERROR; + + pStream->data_type = 0; + pStream->adler = MZ_ADLER32_INIT; + pStream->msg = NULL; + pStream->reserved = 0; + pStream->total_in = 0; + pStream->total_out = 0; + if (!pStream->zalloc) pStream->zalloc = def_alloc_func; + if (!pStream->zfree) pStream->zfree = def_free_func; + + pComp = (tdefl_compressor *)pStream->zalloc(pStream->opaque, 1, sizeof(tdefl_compressor)); + if (!pComp) + return MZ_MEM_ERROR; + + pStream->state = (struct mz_internal_state *)pComp; + + if (tdefl_init(pComp, NULL, NULL, comp_flags) != TDEFL_STATUS_OKAY) + { + mz_deflateEnd(pStream); + return MZ_PARAM_ERROR; + } + + return MZ_OK; +} + +int mz_deflateReset(mz_streamp pStream) +{ + if ((!pStream) || (!pStream->state) || (!pStream->zalloc) || (!pStream->zfree)) return MZ_STREAM_ERROR; + pStream->total_in = pStream->total_out = 0; + tdefl_init((tdefl_compressor*)pStream->state, NULL, NULL, ((tdefl_compressor*)pStream->state)->m_flags); + return MZ_OK; +} + +int mz_deflate(mz_streamp pStream, int flush) +{ + size_t in_bytes, out_bytes; + mz_ulong orig_total_in, orig_total_out; + int mz_status = MZ_OK; + + if ((!pStream) || (!pStream->state) || (flush < 0) || (flush > MZ_FINISH) || (!pStream->next_out)) return MZ_STREAM_ERROR; + if (!pStream->avail_out) return MZ_BUF_ERROR; + + if (flush == MZ_PARTIAL_FLUSH) flush = MZ_SYNC_FLUSH; + + if (((tdefl_compressor*)pStream->state)->m_prev_return_status == TDEFL_STATUS_DONE) + return (flush == MZ_FINISH) ? MZ_STREAM_END : MZ_BUF_ERROR; + + orig_total_in = pStream->total_in; orig_total_out = pStream->total_out; + for ( ; ; ) + { + tdefl_status defl_status; + in_bytes = pStream->avail_in; out_bytes = pStream->avail_out; + + defl_status = tdefl_compress((tdefl_compressor*)pStream->state, pStream->next_in, &in_bytes, pStream->next_out, &out_bytes, (tdefl_flush)flush); + pStream->next_in += (mz_uint)in_bytes; pStream->avail_in -= (mz_uint)in_bytes; + pStream->total_in += (mz_uint)in_bytes; pStream->adler = tdefl_get_adler32((tdefl_compressor*)pStream->state); + + pStream->next_out += (mz_uint)out_bytes; pStream->avail_out -= (mz_uint)out_bytes; + pStream->total_out += (mz_uint)out_bytes; + + if (defl_status < 0) + { + mz_status = MZ_STREAM_ERROR; + break; + } + else if (defl_status == TDEFL_STATUS_DONE) + { + mz_status = MZ_STREAM_END; + break; + } + else if (!pStream->avail_out) + break; + else if ((!pStream->avail_in) && (flush != MZ_FINISH)) + { + if ((flush) || (pStream->total_in != orig_total_in) || (pStream->total_out != orig_total_out)) + break; + return MZ_BUF_ERROR; // Can't make forward progress without some input. + } + } + return mz_status; +} + +int mz_deflateEnd(mz_streamp pStream) +{ + if (!pStream) return MZ_STREAM_ERROR; + if (pStream->state) + { + pStream->zfree(pStream->opaque, pStream->state); + pStream->state = NULL; + } + return MZ_OK; +} + +mz_ulong mz_deflateBound(mz_streamp pStream, mz_ulong source_len) +{ + (void)pStream; + // This is really over conservative. (And lame, but it's actually pretty tricky to compute a true upper bound given the way tdefl's blocking works.) + return MZ_MAX(128 + (source_len * 110) / 100, 128 + source_len + ((source_len / (31 * 1024)) + 1) * 5); +} + +int mz_compress2(unsigned char *pDest, mz_ulong *pDest_len, const unsigned char *pSource, mz_ulong source_len, int level) +{ + int status; + mz_stream stream; + memset(&stream, 0, sizeof(stream)); + + // In case mz_ulong is 64-bits (argh I hate longs). + if ((source_len | *pDest_len) > 0xFFFFFFFFU) return MZ_PARAM_ERROR; + + stream.next_in = pSource; + stream.avail_in = (mz_uint32)source_len; + stream.next_out = pDest; + stream.avail_out = (mz_uint32)*pDest_len; + + status = mz_deflateInit(&stream, level); + if (status != MZ_OK) return status; + + status = mz_deflate(&stream, MZ_FINISH); + if (status != MZ_STREAM_END) + { + mz_deflateEnd(&stream); + return (status == MZ_OK) ? MZ_BUF_ERROR : status; + } + + *pDest_len = stream.total_out; + return mz_deflateEnd(&stream); +} + +int mz_compress(unsigned char *pDest, mz_ulong *pDest_len, const unsigned char *pSource, mz_ulong source_len) +{ + return mz_compress2(pDest, pDest_len, pSource, source_len, MZ_DEFAULT_COMPRESSION); +} + +mz_ulong mz_compressBound(mz_ulong source_len) +{ + return mz_deflateBound(NULL, source_len); +} + +typedef struct +{ + tinfl_decompressor m_decomp; + mz_uint m_dict_ofs, m_dict_avail, m_first_call, m_has_flushed; int m_window_bits; + mz_uint8 m_dict[TINFL_LZ_DICT_SIZE]; + tinfl_status m_last_status; +} inflate_state; + +int mz_inflateInit2(mz_streamp pStream, int window_bits) +{ + inflate_state *pDecomp; + if (!pStream) return MZ_STREAM_ERROR; + if ((window_bits != MZ_DEFAULT_WINDOW_BITS) && (-window_bits != MZ_DEFAULT_WINDOW_BITS)) return MZ_PARAM_ERROR; + + pStream->data_type = 0; + pStream->adler = 0; + pStream->msg = NULL; + pStream->total_in = 0; + pStream->total_out = 0; + pStream->reserved = 0; + if (!pStream->zalloc) pStream->zalloc = def_alloc_func; + if (!pStream->zfree) pStream->zfree = def_free_func; + + pDecomp = (inflate_state*)pStream->zalloc(pStream->opaque, 1, sizeof(inflate_state)); + if (!pDecomp) return MZ_MEM_ERROR; + + pStream->state = (struct mz_internal_state *)pDecomp; + + tinfl_init(&pDecomp->m_decomp); + pDecomp->m_dict_ofs = 0; + pDecomp->m_dict_avail = 0; + pDecomp->m_last_status = TINFL_STATUS_NEEDS_MORE_INPUT; + pDecomp->m_first_call = 1; + pDecomp->m_has_flushed = 0; + pDecomp->m_window_bits = window_bits; + + return MZ_OK; +} + +int mz_inflateInit(mz_streamp pStream) +{ + return mz_inflateInit2(pStream, MZ_DEFAULT_WINDOW_BITS); +} + +int mz_inflate(mz_streamp pStream, int flush) +{ + inflate_state* pState; + mz_uint n, first_call, decomp_flags = TINFL_FLAG_COMPUTE_ADLER32; + size_t in_bytes, out_bytes, orig_avail_in; + tinfl_status status; + + if ((!pStream) || (!pStream->state)) return MZ_STREAM_ERROR; + if (flush == MZ_PARTIAL_FLUSH) flush = MZ_SYNC_FLUSH; + if ((flush) && (flush != MZ_SYNC_FLUSH) && (flush != MZ_FINISH)) return MZ_STREAM_ERROR; + + pState = (inflate_state*)pStream->state; + if (pState->m_window_bits > 0) decomp_flags |= TINFL_FLAG_PARSE_ZLIB_HEADER; + orig_avail_in = pStream->avail_in; + + first_call = pState->m_first_call; pState->m_first_call = 0; + if (pState->m_last_status < 0) return MZ_DATA_ERROR; + + if (pState->m_has_flushed && (flush != MZ_FINISH)) return MZ_STREAM_ERROR; + pState->m_has_flushed |= (flush == MZ_FINISH); + + if ((flush == MZ_FINISH) && (first_call)) + { + // MZ_FINISH on the first call implies that the input and output buffers are large enough to hold the entire compressed/decompressed file. + decomp_flags |= TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF; + in_bytes = pStream->avail_in; out_bytes = pStream->avail_out; + status = tinfl_decompress(&pState->m_decomp, pStream->next_in, &in_bytes, pStream->next_out, pStream->next_out, &out_bytes, decomp_flags); + pState->m_last_status = status; + pStream->next_in += (mz_uint)in_bytes; pStream->avail_in -= (mz_uint)in_bytes; pStream->total_in += (mz_uint)in_bytes; + pStream->adler = tinfl_get_adler32(&pState->m_decomp); + pStream->next_out += (mz_uint)out_bytes; pStream->avail_out -= (mz_uint)out_bytes; pStream->total_out += (mz_uint)out_bytes; + + if (status < 0) + return MZ_DATA_ERROR; + else if (status != TINFL_STATUS_DONE) + { + pState->m_last_status = TINFL_STATUS_FAILED; + return MZ_BUF_ERROR; + } + return MZ_STREAM_END; + } + // flush != MZ_FINISH then we must assume there's more input. + if (flush != MZ_FINISH) decomp_flags |= TINFL_FLAG_HAS_MORE_INPUT; + + if (pState->m_dict_avail) + { + n = MZ_MIN(pState->m_dict_avail, pStream->avail_out); + memcpy(pStream->next_out, pState->m_dict + pState->m_dict_ofs, n); + pStream->next_out += n; pStream->avail_out -= n; pStream->total_out += n; + pState->m_dict_avail -= n; pState->m_dict_ofs = (pState->m_dict_ofs + n) & (TINFL_LZ_DICT_SIZE - 1); + return ((pState->m_last_status == TINFL_STATUS_DONE) && (!pState->m_dict_avail)) ? MZ_STREAM_END : MZ_OK; + } + + for ( ; ; ) + { + in_bytes = pStream->avail_in; + out_bytes = TINFL_LZ_DICT_SIZE - pState->m_dict_ofs; + + status = tinfl_decompress(&pState->m_decomp, pStream->next_in, &in_bytes, pState->m_dict, pState->m_dict + pState->m_dict_ofs, &out_bytes, decomp_flags); + pState->m_last_status = status; + + pStream->next_in += (mz_uint)in_bytes; pStream->avail_in -= (mz_uint)in_bytes; + pStream->total_in += (mz_uint)in_bytes; pStream->adler = tinfl_get_adler32(&pState->m_decomp); + + pState->m_dict_avail = (mz_uint)out_bytes; + + n = MZ_MIN(pState->m_dict_avail, pStream->avail_out); + memcpy(pStream->next_out, pState->m_dict + pState->m_dict_ofs, n); + pStream->next_out += n; pStream->avail_out -= n; pStream->total_out += n; + pState->m_dict_avail -= n; pState->m_dict_ofs = (pState->m_dict_ofs + n) & (TINFL_LZ_DICT_SIZE - 1); + + if (status < 0) + return MZ_DATA_ERROR; // Stream is corrupted (there could be some uncompressed data left in the output dictionary - oh well). + else if ((status == TINFL_STATUS_NEEDS_MORE_INPUT) && (!orig_avail_in)) + return MZ_BUF_ERROR; // Signal caller that we can't make forward progress without supplying more input or by setting flush to MZ_FINISH. + else if (flush == MZ_FINISH) + { + // The output buffer MUST be large to hold the remaining uncompressed data when flush==MZ_FINISH. + if (status == TINFL_STATUS_DONE) + return pState->m_dict_avail ? MZ_BUF_ERROR : MZ_STREAM_END; + // status here must be TINFL_STATUS_HAS_MORE_OUTPUT, which means there's at least 1 more byte on the way. If there's no more room left in the output buffer then something is wrong. + else if (!pStream->avail_out) + return MZ_BUF_ERROR; + } + else if ((status == TINFL_STATUS_DONE) || (!pStream->avail_in) || (!pStream->avail_out) || (pState->m_dict_avail)) + break; + } + + return ((status == TINFL_STATUS_DONE) && (!pState->m_dict_avail)) ? MZ_STREAM_END : MZ_OK; +} + +int mz_inflateEnd(mz_streamp pStream) +{ + if (!pStream) + return MZ_STREAM_ERROR; + if (pStream->state) + { + pStream->zfree(pStream->opaque, pStream->state); + pStream->state = NULL; + } + return MZ_OK; +} + +int mz_uncompress(unsigned char *pDest, mz_ulong *pDest_len, const unsigned char *pSource, mz_ulong source_len) +{ + mz_stream stream; + int status; + memset(&stream, 0, sizeof(stream)); + + // In case mz_ulong is 64-bits (argh I hate longs). + if ((source_len | *pDest_len) > 0xFFFFFFFFU) return MZ_PARAM_ERROR; + + stream.next_in = pSource; + stream.avail_in = (mz_uint32)source_len; + stream.next_out = pDest; + stream.avail_out = (mz_uint32)*pDest_len; + + status = mz_inflateInit(&stream); + if (status != MZ_OK) + return status; + + status = mz_inflate(&stream, MZ_FINISH); + if (status != MZ_STREAM_END) + { + mz_inflateEnd(&stream); + return ((status == MZ_BUF_ERROR) && (!stream.avail_in)) ? MZ_DATA_ERROR : status; + } + *pDest_len = stream.total_out; + + return mz_inflateEnd(&stream); +} + +const char *mz_error(int err) +{ + static const struct { int m_err; const char *m_pDesc; } s_error_descs[] = + { + { MZ_OK, "" }, { MZ_STREAM_END, "stream end" }, { MZ_NEED_DICT, "need dictionary" }, { MZ_ERRNO, "file error" }, { MZ_STREAM_ERROR, "stream error" }, + { MZ_DATA_ERROR, "data error" }, { MZ_MEM_ERROR, "out of memory" }, { MZ_BUF_ERROR, "buf error" }, { MZ_VERSION_ERROR, "version error" }, { MZ_PARAM_ERROR, "parameter error" } + }; + mz_uint i; for (i = 0; i < sizeof(s_error_descs) / sizeof(s_error_descs[0]); ++i) if (s_error_descs[i].m_err == err) return s_error_descs[i].m_pDesc; + return NULL; +} + +#endif //MINIZ_NO_ZLIB_APIS + +// ------------------- Low-level Decompression (completely independent from all compression API's) + +#define TINFL_MEMCPY(d, s, l) memcpy(d, s, l) +#define TINFL_MEMSET(p, c, l) memset(p, c, l) + +#define TINFL_CR_BEGIN switch(r->m_state) { case 0: +#define TINFL_CR_RETURN(state_index, result) do { status = result; r->m_state = state_index; goto common_exit; case state_index:; } MZ_MACRO_END +#define TINFL_CR_RETURN_FOREVER(state_index, result) do { for ( ; ; ) { TINFL_CR_RETURN(state_index, result); } } MZ_MACRO_END +#define TINFL_CR_FINISH } + +// TODO: If the caller has indicated that there's no more input, and we attempt to read beyond the input buf, then something is wrong with the input because the inflator never +// reads ahead more than it needs to. Currently TINFL_GET_BYTE() pads the end of the stream with 0's in this scenario. +#define TINFL_GET_BYTE(state_index, c) do { \ + if (pIn_buf_cur >= pIn_buf_end) { \ + for ( ; ; ) { \ + if (decomp_flags & TINFL_FLAG_HAS_MORE_INPUT) { \ + TINFL_CR_RETURN(state_index, TINFL_STATUS_NEEDS_MORE_INPUT); \ + if (pIn_buf_cur < pIn_buf_end) { \ + c = *pIn_buf_cur++; \ + break; \ + } \ + } else { \ + c = 0; \ + break; \ + } \ + } \ + } else c = *pIn_buf_cur++; } MZ_MACRO_END + +#define TINFL_NEED_BITS(state_index, n) do { mz_uint c; TINFL_GET_BYTE(state_index, c); bit_buf |= (((tinfl_bit_buf_t)c) << num_bits); num_bits += 8; } while (num_bits < (mz_uint)(n)) +#define TINFL_SKIP_BITS(state_index, n) do { if (num_bits < (mz_uint)(n)) { TINFL_NEED_BITS(state_index, n); } bit_buf >>= (n); num_bits -= (n); } MZ_MACRO_END +#define TINFL_GET_BITS(state_index, b, n) do { if (num_bits < (mz_uint)(n)) { TINFL_NEED_BITS(state_index, n); } b = bit_buf & ((1 << (n)) - 1); bit_buf >>= (n); num_bits -= (n); } MZ_MACRO_END + +// TINFL_HUFF_BITBUF_FILL() is only used rarely, when the number of bytes remaining in the input buffer falls below 2. +// It reads just enough bytes from the input stream that are needed to decode the next Huffman code (and absolutely no more). It works by trying to fully decode a +// Huffman code by using whatever bits are currently present in the bit buffer. If this fails, it reads another byte, and tries again until it succeeds or until the +// bit buffer contains >=15 bits (deflate's max. Huffman code size). +#define TINFL_HUFF_BITBUF_FILL(state_index, pHuff) \ + do { \ + temp = (pHuff)->m_look_up[bit_buf & (TINFL_FAST_LOOKUP_SIZE - 1)]; \ + if (temp >= 0) { \ + code_len = temp >> 9; \ + if ((code_len) && (num_bits >= code_len)) \ + break; \ + } else if (num_bits > TINFL_FAST_LOOKUP_BITS) { \ + code_len = TINFL_FAST_LOOKUP_BITS; \ + do { \ + temp = (pHuff)->m_tree[~temp + ((bit_buf >> code_len++) & 1)]; \ + } while ((temp < 0) && (num_bits >= (code_len + 1))); if (temp >= 0) break; \ + } TINFL_GET_BYTE(state_index, c); bit_buf |= (((tinfl_bit_buf_t)c) << num_bits); num_bits += 8; \ + } while (num_bits < 15); + +// TINFL_HUFF_DECODE() decodes the next Huffman coded symbol. It's more complex than you would initially expect because the zlib API expects the decompressor to never read +// beyond the final byte of the deflate stream. (In other words, when this macro wants to read another byte from the input, it REALLY needs another byte in order to fully +// decode the next Huffman code.) Handling this properly is particularly important on raw deflate (non-zlib) streams, which aren't followed by a byte aligned adler-32. +// The slow path is only executed at the very end of the input buffer. +#define TINFL_HUFF_DECODE(state_index, sym, pHuff) do { \ + int temp; mz_uint code_len, c; \ + if (num_bits < 15) { \ + if ((pIn_buf_end - pIn_buf_cur) < 2) { \ + TINFL_HUFF_BITBUF_FILL(state_index, pHuff); \ + } else { \ + bit_buf |= (((tinfl_bit_buf_t)pIn_buf_cur[0]) << num_bits) | (((tinfl_bit_buf_t)pIn_buf_cur[1]) << (num_bits + 8)); pIn_buf_cur += 2; num_bits += 16; \ + } \ + } \ + if ((temp = (pHuff)->m_look_up[bit_buf & (TINFL_FAST_LOOKUP_SIZE - 1)]) >= 0) \ + code_len = temp >> 9, temp &= 511; \ + else { \ + code_len = TINFL_FAST_LOOKUP_BITS; do { temp = (pHuff)->m_tree[~temp + ((bit_buf >> code_len++) & 1)]; } while (temp < 0); \ + } sym = temp; bit_buf >>= code_len; num_bits -= code_len; } MZ_MACRO_END + +tinfl_status tinfl_decompress(tinfl_decompressor *r, const mz_uint8 *pIn_buf_next, size_t *pIn_buf_size, mz_uint8 *pOut_buf_start, mz_uint8 *pOut_buf_next, size_t *pOut_buf_size, const mz_uint32 decomp_flags) +{ + static const int s_length_base[31] = { 3,4,5,6,7,8,9,10,11,13, 15,17,19,23,27,31,35,43,51,59, 67,83,99,115,131,163,195,227,258,0,0 }; + static const int s_length_extra[31]= { 0,0,0,0,0,0,0,0,1,1,1,1,2,2,2,2,3,3,3,3,4,4,4,4,5,5,5,5,0,0,0 }; + static const int s_dist_base[32] = { 1,2,3,4,5,7,9,13,17,25,33,49,65,97,129,193, 257,385,513,769,1025,1537,2049,3073,4097,6145,8193,12289,16385,24577,0,0}; + static const int s_dist_extra[32] = { 0,0,0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,10,10,11,11,12,12,13,13}; + static const mz_uint8 s_length_dezigzag[19] = { 16,17,18,0,8,7,9,6,10,5,11,4,12,3,13,2,14,1,15 }; + static const int s_min_table_sizes[3] = { 257, 1, 4 }; + + tinfl_status status = TINFL_STATUS_FAILED; mz_uint32 num_bits, dist, counter, num_extra; tinfl_bit_buf_t bit_buf; + const mz_uint8 *pIn_buf_cur = pIn_buf_next, *const pIn_buf_end = pIn_buf_next + *pIn_buf_size; + mz_uint8 *pOut_buf_cur = pOut_buf_next, *const pOut_buf_end = pOut_buf_next + *pOut_buf_size; + size_t out_buf_size_mask = (decomp_flags & TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF) ? (size_t)-1 : ((pOut_buf_next - pOut_buf_start) + *pOut_buf_size) - 1, dist_from_out_buf_start; + + // Ensure the output buffer's size is a power of 2, unless the output buffer is large enough to hold the entire output file (in which case it doesn't matter). + if (((out_buf_size_mask + 1) & out_buf_size_mask) || (pOut_buf_next < pOut_buf_start)) { *pIn_buf_size = *pOut_buf_size = 0; return TINFL_STATUS_BAD_PARAM; } + + num_bits = r->m_num_bits; bit_buf = r->m_bit_buf; dist = r->m_dist; counter = r->m_counter; num_extra = r->m_num_extra; dist_from_out_buf_start = r->m_dist_from_out_buf_start; + TINFL_CR_BEGIN + + bit_buf = num_bits = dist = counter = num_extra = r->m_zhdr0 = r->m_zhdr1 = 0; r->m_z_adler32 = r->m_check_adler32 = 1; + if (decomp_flags & TINFL_FLAG_PARSE_ZLIB_HEADER) + { + TINFL_GET_BYTE(1, r->m_zhdr0); TINFL_GET_BYTE(2, r->m_zhdr1); + counter = (((r->m_zhdr0 * 256 + r->m_zhdr1) % 31 != 0) || (r->m_zhdr1 & 32) || ((r->m_zhdr0 & 15) != 8)); + if (!(decomp_flags & TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF)) counter |= (((1U << (8U + (r->m_zhdr0 >> 4))) > 32768U) || ((out_buf_size_mask + 1) < (size_t)(1U << (8U + (r->m_zhdr0 >> 4))))); + if (counter) { TINFL_CR_RETURN_FOREVER(36, TINFL_STATUS_FAILED); } + } + + do + { + TINFL_GET_BITS(3, r->m_final, 3); r->m_type = r->m_final >> 1; + if (r->m_type == 0) + { + TINFL_SKIP_BITS(5, num_bits & 7); + for (counter = 0; counter < 4; ++counter) { if (num_bits) TINFL_GET_BITS(6, r->m_raw_header[counter], 8); else TINFL_GET_BYTE(7, r->m_raw_header[counter]); } + if ((counter = (r->m_raw_header[0] | (r->m_raw_header[1] << 8))) != (mz_uint)(0xFFFF ^ (r->m_raw_header[2] | (r->m_raw_header[3] << 8)))) { TINFL_CR_RETURN_FOREVER(39, TINFL_STATUS_FAILED); } + while ((counter) && (num_bits)) + { + TINFL_GET_BITS(51, dist, 8); + while (pOut_buf_cur >= pOut_buf_end) { TINFL_CR_RETURN(52, TINFL_STATUS_HAS_MORE_OUTPUT); } + *pOut_buf_cur++ = (mz_uint8)dist; + counter--; + } + while (counter) + { + size_t n; while (pOut_buf_cur >= pOut_buf_end) { TINFL_CR_RETURN(9, TINFL_STATUS_HAS_MORE_OUTPUT); } + while (pIn_buf_cur >= pIn_buf_end) + { + if (decomp_flags & TINFL_FLAG_HAS_MORE_INPUT) + { + TINFL_CR_RETURN(38, TINFL_STATUS_NEEDS_MORE_INPUT); + } + else + { + TINFL_CR_RETURN_FOREVER(40, TINFL_STATUS_FAILED); + } + } + n = MZ_MIN(MZ_MIN((size_t)(pOut_buf_end - pOut_buf_cur), (size_t)(pIn_buf_end - pIn_buf_cur)), counter); + TINFL_MEMCPY(pOut_buf_cur, pIn_buf_cur, n); pIn_buf_cur += n; pOut_buf_cur += n; counter -= (mz_uint)n; + } + } + else if (r->m_type == 3) + { + TINFL_CR_RETURN_FOREVER(10, TINFL_STATUS_FAILED); + } + else + { + if (r->m_type == 1) + { + mz_uint8 *p = r->m_tables[0].m_code_size; mz_uint i; + r->m_table_sizes[0] = 288; r->m_table_sizes[1] = 32; TINFL_MEMSET(r->m_tables[1].m_code_size, 5, 32); + for ( i = 0; i <= 143; ++i) *p++ = 8; for ( ; i <= 255; ++i) *p++ = 9; for ( ; i <= 279; ++i) *p++ = 7; for ( ; i <= 287; ++i) *p++ = 8; + } + else + { + for (counter = 0; counter < 3; counter++) { TINFL_GET_BITS(11, r->m_table_sizes[counter], "\05\05\04"[counter]); r->m_table_sizes[counter] += s_min_table_sizes[counter]; } + MZ_CLEAR_OBJ(r->m_tables[2].m_code_size); for (counter = 0; counter < r->m_table_sizes[2]; counter++) { mz_uint s; TINFL_GET_BITS(14, s, 3); r->m_tables[2].m_code_size[s_length_dezigzag[counter]] = (mz_uint8)s; } + r->m_table_sizes[2] = 19; + } + for ( ; (int)r->m_type >= 0; r->m_type--) + { + int tree_next, tree_cur; tinfl_huff_table *pTable; + mz_uint i, j, used_syms, total, sym_index, next_code[17], total_syms[16]; pTable = &r->m_tables[r->m_type]; MZ_CLEAR_OBJ(total_syms); MZ_CLEAR_OBJ(pTable->m_look_up); MZ_CLEAR_OBJ(pTable->m_tree); + for (i = 0; i < r->m_table_sizes[r->m_type]; ++i) total_syms[pTable->m_code_size[i]]++; + used_syms = 0, total = 0; next_code[0] = next_code[1] = 0; + for (i = 1; i <= 15; ++i) { used_syms += total_syms[i]; next_code[i + 1] = (total = ((total + total_syms[i]) << 1)); } + if ((65536 != total) && (used_syms > 1)) + { + TINFL_CR_RETURN_FOREVER(35, TINFL_STATUS_FAILED); + } + for (tree_next = -1, sym_index = 0; sym_index < r->m_table_sizes[r->m_type]; ++sym_index) + { + mz_uint rev_code = 0, l, cur_code, code_size = pTable->m_code_size[sym_index]; if (!code_size) continue; + cur_code = next_code[code_size]++; for (l = code_size; l > 0; l--, cur_code >>= 1) rev_code = (rev_code << 1) | (cur_code & 1); + if (code_size <= TINFL_FAST_LOOKUP_BITS) { mz_int16 k = (mz_int16)((code_size << 9) | sym_index); while (rev_code < TINFL_FAST_LOOKUP_SIZE) { pTable->m_look_up[rev_code] = k; rev_code += (1 << code_size); } continue; } + if (0 == (tree_cur = pTable->m_look_up[rev_code & (TINFL_FAST_LOOKUP_SIZE - 1)])) { pTable->m_look_up[rev_code & (TINFL_FAST_LOOKUP_SIZE - 1)] = (mz_int16)tree_next; tree_cur = tree_next; tree_next -= 2; } + rev_code >>= (TINFL_FAST_LOOKUP_BITS - 1); + for (j = code_size; j > (TINFL_FAST_LOOKUP_BITS + 1); j--) + { + tree_cur -= ((rev_code >>= 1) & 1); + if (!pTable->m_tree[-tree_cur - 1]) { pTable->m_tree[-tree_cur - 1] = (mz_int16)tree_next; tree_cur = tree_next; tree_next -= 2; } else tree_cur = pTable->m_tree[-tree_cur - 1]; + } + tree_cur -= ((rev_code >>= 1) & 1); pTable->m_tree[-tree_cur - 1] = (mz_int16)sym_index; + } + if (r->m_type == 2) + { + for (counter = 0; counter < (r->m_table_sizes[0] + r->m_table_sizes[1]); ) + { + mz_uint s; TINFL_HUFF_DECODE(16, dist, &r->m_tables[2]); if (dist < 16) { r->m_len_codes[counter++] = (mz_uint8)dist; continue; } + if ((dist == 16) && (!counter)) + { + TINFL_CR_RETURN_FOREVER(17, TINFL_STATUS_FAILED); + } + num_extra = "\02\03\07"[dist - 16]; TINFL_GET_BITS(18, s, num_extra); s += "\03\03\013"[dist - 16]; + TINFL_MEMSET(r->m_len_codes + counter, (dist == 16) ? r->m_len_codes[counter - 1] : 0, s); counter += s; + } + if ((r->m_table_sizes[0] + r->m_table_sizes[1]) != counter) + { + TINFL_CR_RETURN_FOREVER(21, TINFL_STATUS_FAILED); + } + TINFL_MEMCPY(r->m_tables[0].m_code_size, r->m_len_codes, r->m_table_sizes[0]); TINFL_MEMCPY(r->m_tables[1].m_code_size, r->m_len_codes + r->m_table_sizes[0], r->m_table_sizes[1]); + } + } + for ( ; ; ) + { + mz_uint8 *pSrc; + for ( ; ; ) + { + if (((pIn_buf_end - pIn_buf_cur) < 4) || ((pOut_buf_end - pOut_buf_cur) < 2)) + { + TINFL_HUFF_DECODE(23, counter, &r->m_tables[0]); + if (counter >= 256) + break; + while (pOut_buf_cur >= pOut_buf_end) { TINFL_CR_RETURN(24, TINFL_STATUS_HAS_MORE_OUTPUT); } + *pOut_buf_cur++ = (mz_uint8)counter; + } + else + { + int sym2; mz_uint code_len; +#if TINFL_USE_64BIT_BITBUF + if (num_bits < 30) { bit_buf |= (((tinfl_bit_buf_t)MZ_READ_LE32(pIn_buf_cur)) << num_bits); pIn_buf_cur += 4; num_bits += 32; } +#else + if (num_bits < 15) { bit_buf |= (((tinfl_bit_buf_t)MZ_READ_LE16(pIn_buf_cur)) << num_bits); pIn_buf_cur += 2; num_bits += 16; } +#endif + if ((sym2 = r->m_tables[0].m_look_up[bit_buf & (TINFL_FAST_LOOKUP_SIZE - 1)]) >= 0) + code_len = sym2 >> 9; + else + { + code_len = TINFL_FAST_LOOKUP_BITS; do { sym2 = r->m_tables[0].m_tree[~sym2 + ((bit_buf >> code_len++) & 1)]; } while (sym2 < 0); + } + counter = sym2; bit_buf >>= code_len; num_bits -= code_len; + if (counter & 256) + break; + +#if !TINFL_USE_64BIT_BITBUF + if (num_bits < 15) { bit_buf |= (((tinfl_bit_buf_t)MZ_READ_LE16(pIn_buf_cur)) << num_bits); pIn_buf_cur += 2; num_bits += 16; } +#endif + if ((sym2 = r->m_tables[0].m_look_up[bit_buf & (TINFL_FAST_LOOKUP_SIZE - 1)]) >= 0) + code_len = sym2 >> 9; + else + { + code_len = TINFL_FAST_LOOKUP_BITS; do { sym2 = r->m_tables[0].m_tree[~sym2 + ((bit_buf >> code_len++) & 1)]; } while (sym2 < 0); + } + bit_buf >>= code_len; num_bits -= code_len; + + pOut_buf_cur[0] = (mz_uint8)counter; + if (sym2 & 256) + { + pOut_buf_cur++; + counter = sym2; + break; + } + pOut_buf_cur[1] = (mz_uint8)sym2; + pOut_buf_cur += 2; + } + } + if ((counter &= 511) == 256) break; + + num_extra = s_length_extra[counter - 257]; counter = s_length_base[counter - 257]; + if (num_extra) { mz_uint extra_bits; TINFL_GET_BITS(25, extra_bits, num_extra); counter += extra_bits; } + + TINFL_HUFF_DECODE(26, dist, &r->m_tables[1]); + num_extra = s_dist_extra[dist]; dist = s_dist_base[dist]; + if (num_extra) { mz_uint extra_bits; TINFL_GET_BITS(27, extra_bits, num_extra); dist += extra_bits; } + + dist_from_out_buf_start = pOut_buf_cur - pOut_buf_start; + if ((dist > dist_from_out_buf_start) && (decomp_flags & TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF)) + { + TINFL_CR_RETURN_FOREVER(37, TINFL_STATUS_FAILED); + } + + pSrc = pOut_buf_start + ((dist_from_out_buf_start - dist) & out_buf_size_mask); + + if ((MZ_MAX(pOut_buf_cur, pSrc) + counter) > pOut_buf_end) + { + while (counter--) + { + while (pOut_buf_cur >= pOut_buf_end) { TINFL_CR_RETURN(53, TINFL_STATUS_HAS_MORE_OUTPUT); } + *pOut_buf_cur++ = pOut_buf_start[(dist_from_out_buf_start++ - dist) & out_buf_size_mask]; + } + continue; + } +#if MINIZ_USE_UNALIGNED_LOADS_AND_STORES + else if ((counter >= 9) && (counter <= dist)) + { + const mz_uint8 *pSrc_end = pSrc + (counter & ~7); + do + { + ((mz_uint32 *)pOut_buf_cur)[0] = ((const mz_uint32 *)pSrc)[0]; + ((mz_uint32 *)pOut_buf_cur)[1] = ((const mz_uint32 *)pSrc)[1]; + pOut_buf_cur += 8; + } while ((pSrc += 8) < pSrc_end); + if ((counter &= 7) < 3) + { + if (counter) + { + pOut_buf_cur[0] = pSrc[0]; + if (counter > 1) + pOut_buf_cur[1] = pSrc[1]; + pOut_buf_cur += counter; + } + continue; + } + } +#endif + do + { + pOut_buf_cur[0] = pSrc[0]; + pOut_buf_cur[1] = pSrc[1]; + pOut_buf_cur[2] = pSrc[2]; + pOut_buf_cur += 3; pSrc += 3; + } while ((int)(counter -= 3) > 2); + if ((int)counter > 0) + { + pOut_buf_cur[0] = pSrc[0]; + if ((int)counter > 1) + pOut_buf_cur[1] = pSrc[1]; + pOut_buf_cur += counter; + } + } + } + } while (!(r->m_final & 1)); + if (decomp_flags & TINFL_FLAG_PARSE_ZLIB_HEADER) + { + TINFL_SKIP_BITS(32, num_bits & 7); for (counter = 0; counter < 4; ++counter) { mz_uint s; if (num_bits) TINFL_GET_BITS(41, s, 8); else TINFL_GET_BYTE(42, s); r->m_z_adler32 = (r->m_z_adler32 << 8) | s; } + } + TINFL_CR_RETURN_FOREVER(34, TINFL_STATUS_DONE); + TINFL_CR_FINISH + +common_exit: + r->m_num_bits = num_bits; r->m_bit_buf = bit_buf; r->m_dist = dist; r->m_counter = counter; r->m_num_extra = num_extra; r->m_dist_from_out_buf_start = dist_from_out_buf_start; + *pIn_buf_size = pIn_buf_cur - pIn_buf_next; *pOut_buf_size = pOut_buf_cur - pOut_buf_next; + if ((decomp_flags & (TINFL_FLAG_PARSE_ZLIB_HEADER | TINFL_FLAG_COMPUTE_ADLER32)) && (status >= 0)) + { + const mz_uint8 *ptr = pOut_buf_next; size_t buf_len = *pOut_buf_size; + mz_uint32 i, s1 = r->m_check_adler32 & 0xffff, s2 = r->m_check_adler32 >> 16; size_t block_len = buf_len % 5552; + while (buf_len) + { + for (i = 0; i + 7 < block_len; i += 8, ptr += 8) + { + s1 += ptr[0], s2 += s1; s1 += ptr[1], s2 += s1; s1 += ptr[2], s2 += s1; s1 += ptr[3], s2 += s1; + s1 += ptr[4], s2 += s1; s1 += ptr[5], s2 += s1; s1 += ptr[6], s2 += s1; s1 += ptr[7], s2 += s1; + } + for ( ; i < block_len; ++i) s1 += *ptr++, s2 += s1; + s1 %= 65521U, s2 %= 65521U; buf_len -= block_len; block_len = 5552; + } + r->m_check_adler32 = (s2 << 16) + s1; if ((status == TINFL_STATUS_DONE) && (decomp_flags & TINFL_FLAG_PARSE_ZLIB_HEADER) && (r->m_check_adler32 != r->m_z_adler32)) status = TINFL_STATUS_ADLER32_MISMATCH; + } + return status; +} + +// Higher level helper functions. +void *tinfl_decompress_mem_to_heap(const void *pSrc_buf, size_t src_buf_len, size_t *pOut_len, int flags) +{ + tinfl_decompressor decomp; void *pBuf = NULL, *pNew_buf; size_t src_buf_ofs = 0, out_buf_capacity = 0; + *pOut_len = 0; + tinfl_init(&decomp); + for ( ; ; ) + { + size_t src_buf_size = src_buf_len - src_buf_ofs, dst_buf_size = out_buf_capacity - *pOut_len, new_out_buf_capacity; + tinfl_status status = tinfl_decompress(&decomp, (const mz_uint8*)pSrc_buf + src_buf_ofs, &src_buf_size, (mz_uint8*)pBuf, pBuf ? (mz_uint8*)pBuf + *pOut_len : NULL, &dst_buf_size, + (flags & ~TINFL_FLAG_HAS_MORE_INPUT) | TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF); + if ((status < 0) || (status == TINFL_STATUS_NEEDS_MORE_INPUT)) + { + MZ_FREE(pBuf); *pOut_len = 0; return NULL; + } + src_buf_ofs += src_buf_size; + *pOut_len += dst_buf_size; + if (status == TINFL_STATUS_DONE) break; + new_out_buf_capacity = out_buf_capacity * 2; if (new_out_buf_capacity < 128) new_out_buf_capacity = 128; + pNew_buf = MZ_REALLOC(pBuf, new_out_buf_capacity); + if (!pNew_buf) + { + MZ_FREE(pBuf); *pOut_len = 0; return NULL; + } + pBuf = pNew_buf; out_buf_capacity = new_out_buf_capacity; + } + return pBuf; +} + +size_t tinfl_decompress_mem_to_mem(void *pOut_buf, size_t out_buf_len, const void *pSrc_buf, size_t src_buf_len, int flags) +{ + tinfl_decompressor decomp; tinfl_status status; tinfl_init(&decomp); + status = tinfl_decompress(&decomp, (const mz_uint8*)pSrc_buf, &src_buf_len, (mz_uint8*)pOut_buf, (mz_uint8*)pOut_buf, &out_buf_len, (flags & ~TINFL_FLAG_HAS_MORE_INPUT) | TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF); + return (status != TINFL_STATUS_DONE) ? TINFL_DECOMPRESS_MEM_TO_MEM_FAILED : out_buf_len; +} + +int tinfl_decompress_mem_to_callback(const void *pIn_buf, size_t *pIn_buf_size, tinfl_put_buf_func_ptr pPut_buf_func, void *pPut_buf_user, int flags) +{ + int result = 0; + tinfl_decompressor decomp; + mz_uint8 *pDict = (mz_uint8*)MZ_MALLOC(TINFL_LZ_DICT_SIZE); size_t in_buf_ofs = 0, dict_ofs = 0; + if (!pDict) + return TINFL_STATUS_FAILED; + tinfl_init(&decomp); + for ( ; ; ) + { + size_t in_buf_size = *pIn_buf_size - in_buf_ofs, dst_buf_size = TINFL_LZ_DICT_SIZE - dict_ofs; + tinfl_status status = tinfl_decompress(&decomp, (const mz_uint8*)pIn_buf + in_buf_ofs, &in_buf_size, pDict, pDict + dict_ofs, &dst_buf_size, + (flags & ~(TINFL_FLAG_HAS_MORE_INPUT | TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF))); + in_buf_ofs += in_buf_size; + if ((dst_buf_size) && (!(*pPut_buf_func)(pDict + dict_ofs, (int)dst_buf_size, pPut_buf_user))) + break; + if (status != TINFL_STATUS_HAS_MORE_OUTPUT) + { + result = (status == TINFL_STATUS_DONE); + break; + } + dict_ofs = (dict_ofs + dst_buf_size) & (TINFL_LZ_DICT_SIZE - 1); + } + MZ_FREE(pDict); + *pIn_buf_size = in_buf_ofs; + return result; +} + +// ------------------- Low-level Compression (independent from all decompression API's) + +// Purposely making these tables static for faster init and thread safety. +static const mz_uint16 s_tdefl_len_sym[256] = { + 257,258,259,260,261,262,263,264,265,265,266,266,267,267,268,268,269,269,269,269,270,270,270,270,271,271,271,271,272,272,272,272, + 273,273,273,273,273,273,273,273,274,274,274,274,274,274,274,274,275,275,275,275,275,275,275,275,276,276,276,276,276,276,276,276, + 277,277,277,277,277,277,277,277,277,277,277,277,277,277,277,277,278,278,278,278,278,278,278,278,278,278,278,278,278,278,278,278, + 279,279,279,279,279,279,279,279,279,279,279,279,279,279,279,279,280,280,280,280,280,280,280,280,280,280,280,280,280,280,280,280, + 281,281,281,281,281,281,281,281,281,281,281,281,281,281,281,281,281,281,281,281,281,281,281,281,281,281,281,281,281,281,281,281, + 282,282,282,282,282,282,282,282,282,282,282,282,282,282,282,282,282,282,282,282,282,282,282,282,282,282,282,282,282,282,282,282, + 283,283,283,283,283,283,283,283,283,283,283,283,283,283,283,283,283,283,283,283,283,283,283,283,283,283,283,283,283,283,283,283, + 284,284,284,284,284,284,284,284,284,284,284,284,284,284,284,284,284,284,284,284,284,284,284,284,284,284,284,284,284,284,284,285 }; + +static const mz_uint8 s_tdefl_len_extra[256] = { + 0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3, + 4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4, + 5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5, + 5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,0 }; + +static const mz_uint8 s_tdefl_small_dist_sym[512] = { + 0,1,2,3,4,4,5,5,6,6,6,6,7,7,7,7,8,8,8,8,8,8,8,8,9,9,9,9,9,9,9,9,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,11,11,11,11,11,11, + 11,11,11,11,11,11,11,11,11,11,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,13, + 13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,14,14,14,14,14,14,14,14,14,14,14,14, + 14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14, + 14,14,14,14,14,14,14,14,14,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15, + 15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,16,16,16,16,16,16,16,16,16,16,16,16,16, + 16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16, + 16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16, + 16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,17,17,17,17,17,17,17,17,17,17,17,17,17,17, + 17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17, + 17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17, + 17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17 }; + +static const mz_uint8 s_tdefl_small_dist_extra[512] = { + 0,0,0,0,1,1,1,1,2,2,2,2,2,2,2,2,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,5,5,5,5,5,5,5,5, + 5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6, + 6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6, + 6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, + 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, + 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, + 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, + 7,7,7,7,7,7,7,7 }; + +static const mz_uint8 s_tdefl_large_dist_sym[128] = { + 0,0,18,19,20,20,21,21,22,22,22,22,23,23,23,23,24,24,24,24,24,24,24,24,25,25,25,25,25,25,25,25,26,26,26,26,26,26,26,26,26,26,26,26, + 26,26,26,26,27,27,27,27,27,27,27,27,27,27,27,27,27,27,27,27,28,28,28,28,28,28,28,28,28,28,28,28,28,28,28,28,28,28,28,28,28,28,28,28, + 28,28,28,28,28,28,28,28,29,29,29,29,29,29,29,29,29,29,29,29,29,29,29,29,29,29,29,29,29,29,29,29,29,29,29,29,29,29,29,29 }; + +static const mz_uint8 s_tdefl_large_dist_extra[128] = { + 0,0,8,8,9,9,9,9,10,10,10,10,10,10,10,10,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12, + 12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13, + 13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13 }; + +// Radix sorts tdefl_sym_freq[] array by 16-bit key m_key. Returns ptr to sorted values. +typedef struct { mz_uint16 m_key, m_sym_index; } tdefl_sym_freq; +static tdefl_sym_freq* tdefl_radix_sort_syms(mz_uint num_syms, tdefl_sym_freq* pSyms0, tdefl_sym_freq* pSyms1) +{ + mz_uint32 total_passes = 2, pass_shift, pass, i, hist[256 * 2]; tdefl_sym_freq* pCur_syms = pSyms0, *pNew_syms = pSyms1; MZ_CLEAR_OBJ(hist); + for (i = 0; i < num_syms; i++) { mz_uint freq = pSyms0[i].m_key; hist[freq & 0xFF]++; hist[256 + ((freq >> 8) & 0xFF)]++; } + while ((total_passes > 1) && (num_syms == hist[(total_passes - 1) * 256])) total_passes--; + for (pass_shift = 0, pass = 0; pass < total_passes; pass++, pass_shift += 8) + { + const mz_uint32* pHist = &hist[pass << 8]; + mz_uint offsets[256], cur_ofs = 0; + for (i = 0; i < 256; i++) { offsets[i] = cur_ofs; cur_ofs += pHist[i]; } + for (i = 0; i < num_syms; i++) pNew_syms[offsets[(pCur_syms[i].m_key >> pass_shift) & 0xFF]++] = pCur_syms[i]; + { tdefl_sym_freq* t = pCur_syms; pCur_syms = pNew_syms; pNew_syms = t; } + } + return pCur_syms; +} + +// tdefl_calculate_minimum_redundancy() originally written by: Alistair Moffat, alistair@cs.mu.oz.au, Jyrki Katajainen, jyrki@diku.dk, November 1996. +static void tdefl_calculate_minimum_redundancy(tdefl_sym_freq *A, int n) +{ + int root, leaf, next, avbl, used, dpth; + if (n==0) return; else if (n==1) { A[0].m_key = 1; return; } + A[0].m_key += A[1].m_key; root = 0; leaf = 2; + for (next=1; next < n-1; next++) + { + if (leaf>=n || A[root].m_key=n || (root=0; next--) A[next].m_key = A[A[next].m_key].m_key+1; + avbl = 1; used = dpth = 0; root = n-2; next = n-1; + while (avbl>0) + { + while (root>=0 && (int)A[root].m_key==dpth) { used++; root--; } + while (avbl>used) { A[next--].m_key = (mz_uint16)(dpth); avbl--; } + avbl = 2*used; dpth++; used = 0; + } +} + +// Limits canonical Huffman code table's max code size. +enum { TDEFL_MAX_SUPPORTED_HUFF_CODESIZE = 32 }; +static void tdefl_huffman_enforce_max_code_size(int *pNum_codes, int code_list_len, int max_code_size) +{ + int i; mz_uint32 total = 0; if (code_list_len <= 1) return; + for (i = max_code_size + 1; i <= TDEFL_MAX_SUPPORTED_HUFF_CODESIZE; i++) pNum_codes[max_code_size] += pNum_codes[i]; + for (i = max_code_size; i > 0; i--) total += (((mz_uint32)pNum_codes[i]) << (max_code_size - i)); + while (total != (1UL << max_code_size)) + { + pNum_codes[max_code_size]--; + for (i = max_code_size - 1; i > 0; i--) if (pNum_codes[i]) { pNum_codes[i]--; pNum_codes[i + 1] += 2; break; } + total--; + } +} + +static void tdefl_optimize_huffman_table(tdefl_compressor *d, int table_num, int table_len, int code_size_limit, int static_table) +{ + int i, j, l, num_codes[1 + TDEFL_MAX_SUPPORTED_HUFF_CODESIZE]; mz_uint next_code[TDEFL_MAX_SUPPORTED_HUFF_CODESIZE + 1]; MZ_CLEAR_OBJ(num_codes); + if (static_table) + { + for (i = 0; i < table_len; i++) num_codes[d->m_huff_code_sizes[table_num][i]]++; + } + else + { + tdefl_sym_freq syms0[TDEFL_MAX_HUFF_SYMBOLS], syms1[TDEFL_MAX_HUFF_SYMBOLS], *pSyms; + int num_used_syms = 0; + const mz_uint16 *pSym_count = &d->m_huff_count[table_num][0]; + for (i = 0; i < table_len; i++) if (pSym_count[i]) { syms0[num_used_syms].m_key = (mz_uint16)pSym_count[i]; syms0[num_used_syms++].m_sym_index = (mz_uint16)i; } + + pSyms = tdefl_radix_sort_syms(num_used_syms, syms0, syms1); tdefl_calculate_minimum_redundancy(pSyms, num_used_syms); + + for (i = 0; i < num_used_syms; i++) num_codes[pSyms[i].m_key]++; + + tdefl_huffman_enforce_max_code_size(num_codes, num_used_syms, code_size_limit); + + MZ_CLEAR_OBJ(d->m_huff_code_sizes[table_num]); MZ_CLEAR_OBJ(d->m_huff_codes[table_num]); + for (i = 1, j = num_used_syms; i <= code_size_limit; i++) + for (l = num_codes[i]; l > 0; l--) d->m_huff_code_sizes[table_num][pSyms[--j].m_sym_index] = (mz_uint8)(i); + } + + next_code[1] = 0; for (j = 0, i = 2; i <= code_size_limit; i++) next_code[i] = j = ((j + num_codes[i - 1]) << 1); + + for (i = 0; i < table_len; i++) + { + mz_uint rev_code = 0, code, code_size; if ((code_size = d->m_huff_code_sizes[table_num][i]) == 0) continue; + code = next_code[code_size]++; for (l = code_size; l > 0; l--, code >>= 1) rev_code = (rev_code << 1) | (code & 1); + d->m_huff_codes[table_num][i] = (mz_uint16)rev_code; + } +} + +#define TDEFL_PUT_BITS(b, l) do { \ + mz_uint bits = b; mz_uint len = l; MZ_ASSERT(bits <= ((1U << len) - 1U)); \ + d->m_bit_buffer |= (bits << d->m_bits_in); d->m_bits_in += len; \ + while (d->m_bits_in >= 8) { \ + if (d->m_pOutput_buf < d->m_pOutput_buf_end) \ + *d->m_pOutput_buf++ = (mz_uint8)(d->m_bit_buffer); \ + d->m_bit_buffer >>= 8; \ + d->m_bits_in -= 8; \ + } \ +} MZ_MACRO_END + +#define TDEFL_RLE_PREV_CODE_SIZE() { if (rle_repeat_count) { \ + if (rle_repeat_count < 3) { \ + d->m_huff_count[2][prev_code_size] = (mz_uint16)(d->m_huff_count[2][prev_code_size] + rle_repeat_count); \ + while (rle_repeat_count--) packed_code_sizes[num_packed_code_sizes++] = prev_code_size; \ + } else { \ + d->m_huff_count[2][16] = (mz_uint16)(d->m_huff_count[2][16] + 1); packed_code_sizes[num_packed_code_sizes++] = 16; packed_code_sizes[num_packed_code_sizes++] = (mz_uint8)(rle_repeat_count - 3); \ +} rle_repeat_count = 0; } } + +#define TDEFL_RLE_ZERO_CODE_SIZE() { if (rle_z_count) { \ + if (rle_z_count < 3) { \ + d->m_huff_count[2][0] = (mz_uint16)(d->m_huff_count[2][0] + rle_z_count); while (rle_z_count--) packed_code_sizes[num_packed_code_sizes++] = 0; \ + } else if (rle_z_count <= 10) { \ + d->m_huff_count[2][17] = (mz_uint16)(d->m_huff_count[2][17] + 1); packed_code_sizes[num_packed_code_sizes++] = 17; packed_code_sizes[num_packed_code_sizes++] = (mz_uint8)(rle_z_count - 3); \ + } else { \ + d->m_huff_count[2][18] = (mz_uint16)(d->m_huff_count[2][18] + 1); packed_code_sizes[num_packed_code_sizes++] = 18; packed_code_sizes[num_packed_code_sizes++] = (mz_uint8)(rle_z_count - 11); \ +} rle_z_count = 0; } } + +static mz_uint8 s_tdefl_packed_code_size_syms_swizzle[] = { 16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15 }; + +static void tdefl_start_dynamic_block(tdefl_compressor *d) +{ + int num_lit_codes, num_dist_codes, num_bit_lengths; mz_uint i, total_code_sizes_to_pack, num_packed_code_sizes, rle_z_count, rle_repeat_count, packed_code_sizes_index; + mz_uint8 code_sizes_to_pack[TDEFL_MAX_HUFF_SYMBOLS_0 + TDEFL_MAX_HUFF_SYMBOLS_1], packed_code_sizes[TDEFL_MAX_HUFF_SYMBOLS_0 + TDEFL_MAX_HUFF_SYMBOLS_1], prev_code_size = 0xFF; + + d->m_huff_count[0][256] = 1; + + tdefl_optimize_huffman_table(d, 0, TDEFL_MAX_HUFF_SYMBOLS_0, 15, MZ_FALSE); + tdefl_optimize_huffman_table(d, 1, TDEFL_MAX_HUFF_SYMBOLS_1, 15, MZ_FALSE); + + for (num_lit_codes = 286; num_lit_codes > 257; num_lit_codes--) if (d->m_huff_code_sizes[0][num_lit_codes - 1]) break; + for (num_dist_codes = 30; num_dist_codes > 1; num_dist_codes--) if (d->m_huff_code_sizes[1][num_dist_codes - 1]) break; + + memcpy(code_sizes_to_pack, &d->m_huff_code_sizes[0][0], num_lit_codes); + memcpy(code_sizes_to_pack + num_lit_codes, &d->m_huff_code_sizes[1][0], num_dist_codes); + total_code_sizes_to_pack = num_lit_codes + num_dist_codes; num_packed_code_sizes = 0; rle_z_count = 0; rle_repeat_count = 0; + + memset(&d->m_huff_count[2][0], 0, sizeof(d->m_huff_count[2][0]) * TDEFL_MAX_HUFF_SYMBOLS_2); + for (i = 0; i < total_code_sizes_to_pack; i++) + { + mz_uint8 code_size = code_sizes_to_pack[i]; + if (!code_size) + { + TDEFL_RLE_PREV_CODE_SIZE(); + if (++rle_z_count == 138) { TDEFL_RLE_ZERO_CODE_SIZE(); } + } + else + { + TDEFL_RLE_ZERO_CODE_SIZE(); + if (code_size != prev_code_size) + { + TDEFL_RLE_PREV_CODE_SIZE(); + d->m_huff_count[2][code_size] = (mz_uint16)(d->m_huff_count[2][code_size] + 1); packed_code_sizes[num_packed_code_sizes++] = code_size; + } + else if (++rle_repeat_count == 6) + { + TDEFL_RLE_PREV_CODE_SIZE(); + } + } + prev_code_size = code_size; + } + if (rle_repeat_count) { TDEFL_RLE_PREV_CODE_SIZE(); } else { TDEFL_RLE_ZERO_CODE_SIZE(); } + + tdefl_optimize_huffman_table(d, 2, TDEFL_MAX_HUFF_SYMBOLS_2, 7, MZ_FALSE); + + TDEFL_PUT_BITS(2, 2); + + TDEFL_PUT_BITS(num_lit_codes - 257, 5); + TDEFL_PUT_BITS(num_dist_codes - 1, 5); + + for (num_bit_lengths = 18; num_bit_lengths >= 0; num_bit_lengths--) if (d->m_huff_code_sizes[2][s_tdefl_packed_code_size_syms_swizzle[num_bit_lengths]]) break; + num_bit_lengths = MZ_MAX(4, (num_bit_lengths + 1)); TDEFL_PUT_BITS(num_bit_lengths - 4, 4); + for (i = 0; (int)i < num_bit_lengths; i++) TDEFL_PUT_BITS(d->m_huff_code_sizes[2][s_tdefl_packed_code_size_syms_swizzle[i]], 3); + + for (packed_code_sizes_index = 0; packed_code_sizes_index < num_packed_code_sizes; ) + { + mz_uint code = packed_code_sizes[packed_code_sizes_index++]; MZ_ASSERT(code < TDEFL_MAX_HUFF_SYMBOLS_2); + TDEFL_PUT_BITS(d->m_huff_codes[2][code], d->m_huff_code_sizes[2][code]); + if (code >= 16) TDEFL_PUT_BITS(packed_code_sizes[packed_code_sizes_index++], "\02\03\07"[code - 16]); + } +} + +static void tdefl_start_static_block(tdefl_compressor *d) +{ + mz_uint i; + mz_uint8 *p = &d->m_huff_code_sizes[0][0]; + + for (i = 0; i <= 143; ++i) *p++ = 8; + for ( ; i <= 255; ++i) *p++ = 9; + for ( ; i <= 279; ++i) *p++ = 7; + for ( ; i <= 287; ++i) *p++ = 8; + + memset(d->m_huff_code_sizes[1], 5, 32); + + tdefl_optimize_huffman_table(d, 0, 288, 15, MZ_TRUE); + tdefl_optimize_huffman_table(d, 1, 32, 15, MZ_TRUE); + + TDEFL_PUT_BITS(1, 2); +} + +static const mz_uint mz_bitmasks[17] = { 0x0000, 0x0001, 0x0003, 0x0007, 0x000F, 0x001F, 0x003F, 0x007F, 0x00FF, 0x01FF, 0x03FF, 0x07FF, 0x0FFF, 0x1FFF, 0x3FFF, 0x7FFF, 0xFFFF }; + +#if MINIZ_USE_UNALIGNED_LOADS_AND_STORES && MINIZ_LITTLE_ENDIAN && MINIZ_HAS_64BIT_REGISTERS +static mz_bool tdefl_compress_lz_codes(tdefl_compressor *d) +{ + mz_uint flags; + mz_uint8 *pLZ_codes; + mz_uint8 *pOutput_buf = d->m_pOutput_buf; + mz_uint8 *pLZ_code_buf_end = d->m_pLZ_code_buf; + mz_uint64 bit_buffer = d->m_bit_buffer; + mz_uint bits_in = d->m_bits_in; + +#define TDEFL_PUT_BITS_FAST(b, l) { bit_buffer |= (((mz_uint64)(b)) << bits_in); bits_in += (l); } + + flags = 1; + for (pLZ_codes = d->m_lz_code_buf; pLZ_codes < pLZ_code_buf_end; flags >>= 1) + { + if (flags == 1) + flags = *pLZ_codes++ | 0x100; + + if (flags & 1) + { + mz_uint s0, s1, n0, n1, sym, num_extra_bits; + mz_uint match_len = pLZ_codes[0], match_dist = *(const mz_uint16 *)(pLZ_codes + 1); pLZ_codes += 3; + + MZ_ASSERT(d->m_huff_code_sizes[0][s_tdefl_len_sym[match_len]]); + TDEFL_PUT_BITS_FAST(d->m_huff_codes[0][s_tdefl_len_sym[match_len]], d->m_huff_code_sizes[0][s_tdefl_len_sym[match_len]]); + TDEFL_PUT_BITS_FAST(match_len & mz_bitmasks[s_tdefl_len_extra[match_len]], s_tdefl_len_extra[match_len]); + + // This sequence coaxes MSVC into using cmov's vs. jmp's. + s0 = s_tdefl_small_dist_sym[match_dist & 511]; + n0 = s_tdefl_small_dist_extra[match_dist & 511]; + s1 = s_tdefl_large_dist_sym[match_dist >> 8]; + n1 = s_tdefl_large_dist_extra[match_dist >> 8]; + sym = (match_dist < 512) ? s0 : s1; + num_extra_bits = (match_dist < 512) ? n0 : n1; + + MZ_ASSERT(d->m_huff_code_sizes[1][sym]); + TDEFL_PUT_BITS_FAST(d->m_huff_codes[1][sym], d->m_huff_code_sizes[1][sym]); + TDEFL_PUT_BITS_FAST(match_dist & mz_bitmasks[num_extra_bits], num_extra_bits); + } + else + { + mz_uint lit = *pLZ_codes++; + MZ_ASSERT(d->m_huff_code_sizes[0][lit]); + TDEFL_PUT_BITS_FAST(d->m_huff_codes[0][lit], d->m_huff_code_sizes[0][lit]); + + if (((flags & 2) == 0) && (pLZ_codes < pLZ_code_buf_end)) + { + flags >>= 1; + lit = *pLZ_codes++; + MZ_ASSERT(d->m_huff_code_sizes[0][lit]); + TDEFL_PUT_BITS_FAST(d->m_huff_codes[0][lit], d->m_huff_code_sizes[0][lit]); + + if (((flags & 2) == 0) && (pLZ_codes < pLZ_code_buf_end)) + { + flags >>= 1; + lit = *pLZ_codes++; + MZ_ASSERT(d->m_huff_code_sizes[0][lit]); + TDEFL_PUT_BITS_FAST(d->m_huff_codes[0][lit], d->m_huff_code_sizes[0][lit]); + } + } + } + + if (pOutput_buf >= d->m_pOutput_buf_end) + return MZ_FALSE; + + *(mz_uint64*)pOutput_buf = bit_buffer; + pOutput_buf += (bits_in >> 3); + bit_buffer >>= (bits_in & ~7); + bits_in &= 7; + } + +#undef TDEFL_PUT_BITS_FAST + + d->m_pOutput_buf = pOutput_buf; + d->m_bits_in = 0; + d->m_bit_buffer = 0; + + while (bits_in) + { + mz_uint32 n = MZ_MIN(bits_in, 16); + TDEFL_PUT_BITS((mz_uint)bit_buffer & mz_bitmasks[n], n); + bit_buffer >>= n; + bits_in -= n; + } + + TDEFL_PUT_BITS(d->m_huff_codes[0][256], d->m_huff_code_sizes[0][256]); + + return (d->m_pOutput_buf < d->m_pOutput_buf_end); +} +#else +static mz_bool tdefl_compress_lz_codes(tdefl_compressor *d) +{ + mz_uint flags; + mz_uint8 *pLZ_codes; + + flags = 1; + for (pLZ_codes = d->m_lz_code_buf; pLZ_codes < d->m_pLZ_code_buf; flags >>= 1) + { + if (flags == 1) + flags = *pLZ_codes++ | 0x100; + if (flags & 1) + { + mz_uint sym, num_extra_bits; + mz_uint match_len = pLZ_codes[0], match_dist = (pLZ_codes[1] | (pLZ_codes[2] << 8)); pLZ_codes += 3; + + MZ_ASSERT(d->m_huff_code_sizes[0][s_tdefl_len_sym[match_len]]); + TDEFL_PUT_BITS(d->m_huff_codes[0][s_tdefl_len_sym[match_len]], d->m_huff_code_sizes[0][s_tdefl_len_sym[match_len]]); + TDEFL_PUT_BITS(match_len & mz_bitmasks[s_tdefl_len_extra[match_len]], s_tdefl_len_extra[match_len]); + + if (match_dist < 512) + { + sym = s_tdefl_small_dist_sym[match_dist]; num_extra_bits = s_tdefl_small_dist_extra[match_dist]; + } + else + { + sym = s_tdefl_large_dist_sym[match_dist >> 8]; num_extra_bits = s_tdefl_large_dist_extra[match_dist >> 8]; + } + MZ_ASSERT(d->m_huff_code_sizes[1][sym]); + TDEFL_PUT_BITS(d->m_huff_codes[1][sym], d->m_huff_code_sizes[1][sym]); + TDEFL_PUT_BITS(match_dist & mz_bitmasks[num_extra_bits], num_extra_bits); + } + else + { + mz_uint lit = *pLZ_codes++; + MZ_ASSERT(d->m_huff_code_sizes[0][lit]); + TDEFL_PUT_BITS(d->m_huff_codes[0][lit], d->m_huff_code_sizes[0][lit]); + } + } + + TDEFL_PUT_BITS(d->m_huff_codes[0][256], d->m_huff_code_sizes[0][256]); + + return (d->m_pOutput_buf < d->m_pOutput_buf_end); +} +#endif // MINIZ_USE_UNALIGNED_LOADS_AND_STORES && MINIZ_LITTLE_ENDIAN && MINIZ_HAS_64BIT_REGISTERS + +static mz_bool tdefl_compress_block(tdefl_compressor *d, mz_bool static_block) +{ + if (static_block) + tdefl_start_static_block(d); + else + tdefl_start_dynamic_block(d); + return tdefl_compress_lz_codes(d); +} + +static int tdefl_flush_block(tdefl_compressor *d, int flush) +{ + mz_uint saved_bit_buf, saved_bits_in; + mz_uint8 *pSaved_output_buf; + mz_bool comp_block_succeeded = MZ_FALSE; + int n, use_raw_block = ((d->m_flags & TDEFL_FORCE_ALL_RAW_BLOCKS) != 0) && (d->m_lookahead_pos - d->m_lz_code_buf_dict_pos) <= d->m_dict_size; + mz_uint8 *pOutput_buf_start = ((d->m_pPut_buf_func == NULL) && ((*d->m_pOut_buf_size - d->m_out_buf_ofs) >= TDEFL_OUT_BUF_SIZE)) ? ((mz_uint8 *)d->m_pOut_buf + d->m_out_buf_ofs) : d->m_output_buf; + + d->m_pOutput_buf = pOutput_buf_start; + d->m_pOutput_buf_end = d->m_pOutput_buf + TDEFL_OUT_BUF_SIZE - 16; + + MZ_ASSERT(!d->m_output_flush_remaining); + d->m_output_flush_ofs = 0; + d->m_output_flush_remaining = 0; + + *d->m_pLZ_flags = (mz_uint8)(*d->m_pLZ_flags >> d->m_num_flags_left); + d->m_pLZ_code_buf -= (d->m_num_flags_left == 8); + + if ((d->m_flags & TDEFL_WRITE_ZLIB_HEADER) && (!d->m_block_index)) + { + TDEFL_PUT_BITS(0x78, 8); TDEFL_PUT_BITS(0x01, 8); + } + + TDEFL_PUT_BITS(flush == TDEFL_FINISH, 1); + + pSaved_output_buf = d->m_pOutput_buf; saved_bit_buf = d->m_bit_buffer; saved_bits_in = d->m_bits_in; + + if (!use_raw_block) + comp_block_succeeded = tdefl_compress_block(d, (d->m_flags & TDEFL_FORCE_ALL_STATIC_BLOCKS) || (d->m_total_lz_bytes < 48)); + + // If the block gets expanded, forget the current contents of the output buffer and send a raw block instead. + if ( ((use_raw_block) || ((d->m_total_lz_bytes) && ((d->m_pOutput_buf - pSaved_output_buf + 1U) >= d->m_total_lz_bytes))) && + ((d->m_lookahead_pos - d->m_lz_code_buf_dict_pos) <= d->m_dict_size) ) + { + mz_uint i; d->m_pOutput_buf = pSaved_output_buf; d->m_bit_buffer = saved_bit_buf, d->m_bits_in = saved_bits_in; + TDEFL_PUT_BITS(0, 2); + if (d->m_bits_in) { TDEFL_PUT_BITS(0, 8 - d->m_bits_in); } + for (i = 2; i; --i, d->m_total_lz_bytes ^= 0xFFFF) + { + TDEFL_PUT_BITS(d->m_total_lz_bytes & 0xFFFF, 16); + } + for (i = 0; i < d->m_total_lz_bytes; ++i) + { + TDEFL_PUT_BITS(d->m_dict[(d->m_lz_code_buf_dict_pos + i) & TDEFL_LZ_DICT_SIZE_MASK], 8); + } + } + // Check for the extremely unlikely (if not impossible) case of the compressed block not fitting into the output buffer when using dynamic codes. + else if (!comp_block_succeeded) + { + d->m_pOutput_buf = pSaved_output_buf; d->m_bit_buffer = saved_bit_buf, d->m_bits_in = saved_bits_in; + tdefl_compress_block(d, MZ_TRUE); + } + + if (flush) + { + if (flush == TDEFL_FINISH) + { + if (d->m_bits_in) { TDEFL_PUT_BITS(0, 8 - d->m_bits_in); } + if (d->m_flags & TDEFL_WRITE_ZLIB_HEADER) { mz_uint i, a = d->m_adler32; for (i = 0; i < 4; i++) { TDEFL_PUT_BITS((a >> 24) & 0xFF, 8); a <<= 8; } } + } + else + { + mz_uint i, z = 0; TDEFL_PUT_BITS(0, 3); if (d->m_bits_in) { TDEFL_PUT_BITS(0, 8 - d->m_bits_in); } for (i = 2; i; --i, z ^= 0xFFFF) { TDEFL_PUT_BITS(z & 0xFFFF, 16); } + } + } + + MZ_ASSERT(d->m_pOutput_buf < d->m_pOutput_buf_end); + + memset(&d->m_huff_count[0][0], 0, sizeof(d->m_huff_count[0][0]) * TDEFL_MAX_HUFF_SYMBOLS_0); + memset(&d->m_huff_count[1][0], 0, sizeof(d->m_huff_count[1][0]) * TDEFL_MAX_HUFF_SYMBOLS_1); + + d->m_pLZ_code_buf = d->m_lz_code_buf + 1; d->m_pLZ_flags = d->m_lz_code_buf; d->m_num_flags_left = 8; d->m_lz_code_buf_dict_pos += d->m_total_lz_bytes; d->m_total_lz_bytes = 0; d->m_block_index++; + + if ((n = (int)(d->m_pOutput_buf - pOutput_buf_start)) != 0) + { + if (d->m_pPut_buf_func) + { + *d->m_pIn_buf_size = d->m_pSrc - (const mz_uint8 *)d->m_pIn_buf; + if (!(*d->m_pPut_buf_func)(d->m_output_buf, n, d->m_pPut_buf_user)) + return (d->m_prev_return_status = TDEFL_STATUS_PUT_BUF_FAILED); + } + else if (pOutput_buf_start == d->m_output_buf) + { + int bytes_to_copy = (int)MZ_MIN((size_t)n, (size_t)(*d->m_pOut_buf_size - d->m_out_buf_ofs)); + memcpy((mz_uint8 *)d->m_pOut_buf + d->m_out_buf_ofs, d->m_output_buf, bytes_to_copy); + d->m_out_buf_ofs += bytes_to_copy; + if ((n -= bytes_to_copy) != 0) + { + d->m_output_flush_ofs = bytes_to_copy; + d->m_output_flush_remaining = n; + } + } + else + { + d->m_out_buf_ofs += n; + } + } + + return d->m_output_flush_remaining; +} + +#if MINIZ_USE_UNALIGNED_LOADS_AND_STORES +#define TDEFL_READ_UNALIGNED_WORD(p) *(const mz_uint16*)(p) +static MZ_FORCEINLINE void tdefl_find_match(tdefl_compressor *d, mz_uint lookahead_pos, mz_uint max_dist, mz_uint max_match_len, mz_uint *pMatch_dist, mz_uint *pMatch_len) +{ + mz_uint dist, pos = lookahead_pos & TDEFL_LZ_DICT_SIZE_MASK, match_len = *pMatch_len, probe_pos = pos, next_probe_pos, probe_len; + mz_uint num_probes_left = d->m_max_probes[match_len >= 32]; + const mz_uint16 *s = (const mz_uint16*)(d->m_dict + pos), *p, *q; + mz_uint16 c01 = TDEFL_READ_UNALIGNED_WORD(&d->m_dict[pos + match_len - 1]), s01 = TDEFL_READ_UNALIGNED_WORD(s); + MZ_ASSERT(max_match_len <= TDEFL_MAX_MATCH_LEN); if (max_match_len <= match_len) return; + for ( ; ; ) + { + for ( ; ; ) + { + if (--num_probes_left == 0) return; + #define TDEFL_PROBE \ + next_probe_pos = d->m_next[probe_pos]; \ + if ((!next_probe_pos) || ((dist = (mz_uint16)(lookahead_pos - next_probe_pos)) > max_dist)) return; \ + probe_pos = next_probe_pos & TDEFL_LZ_DICT_SIZE_MASK; \ + if (TDEFL_READ_UNALIGNED_WORD(&d->m_dict[probe_pos + match_len - 1]) == c01) break; + TDEFL_PROBE; TDEFL_PROBE; TDEFL_PROBE; + } + if (!dist) break; q = (const mz_uint16*)(d->m_dict + probe_pos); if (TDEFL_READ_UNALIGNED_WORD(q) != s01) continue; p = s; probe_len = 32; + do { } while ( (TDEFL_READ_UNALIGNED_WORD(++p) == TDEFL_READ_UNALIGNED_WORD(++q)) && (TDEFL_READ_UNALIGNED_WORD(++p) == TDEFL_READ_UNALIGNED_WORD(++q)) && + (TDEFL_READ_UNALIGNED_WORD(++p) == TDEFL_READ_UNALIGNED_WORD(++q)) && (TDEFL_READ_UNALIGNED_WORD(++p) == TDEFL_READ_UNALIGNED_WORD(++q)) && (--probe_len > 0) ); + if (!probe_len) + { + *pMatch_dist = dist; *pMatch_len = MZ_MIN(max_match_len, TDEFL_MAX_MATCH_LEN); break; + } + else if ((probe_len = ((mz_uint)(p - s) * 2) + (mz_uint)(*(const mz_uint8*)p == *(const mz_uint8*)q)) > match_len) + { + *pMatch_dist = dist; if ((*pMatch_len = match_len = MZ_MIN(max_match_len, probe_len)) == max_match_len) break; + c01 = TDEFL_READ_UNALIGNED_WORD(&d->m_dict[pos + match_len - 1]); + } + } +} +#else +static MZ_FORCEINLINE void tdefl_find_match(tdefl_compressor *d, mz_uint lookahead_pos, mz_uint max_dist, mz_uint max_match_len, mz_uint *pMatch_dist, mz_uint *pMatch_len) +{ + mz_uint dist, pos = lookahead_pos & TDEFL_LZ_DICT_SIZE_MASK, match_len = *pMatch_len, probe_pos = pos, next_probe_pos, probe_len; + mz_uint num_probes_left = d->m_max_probes[match_len >= 32]; + const mz_uint8 *s = d->m_dict + pos, *p, *q; + mz_uint8 c0 = d->m_dict[pos + match_len], c1 = d->m_dict[pos + match_len - 1]; + MZ_ASSERT(max_match_len <= TDEFL_MAX_MATCH_LEN); if (max_match_len <= match_len) return; + for ( ; ; ) + { + for ( ; ; ) + { + if (--num_probes_left == 0) return; + #define TDEFL_PROBE \ + next_probe_pos = d->m_next[probe_pos]; \ + if ((!next_probe_pos) || ((dist = (mz_uint16)(lookahead_pos - next_probe_pos)) > max_dist)) return; \ + probe_pos = next_probe_pos & TDEFL_LZ_DICT_SIZE_MASK; \ + if ((d->m_dict[probe_pos + match_len] == c0) && (d->m_dict[probe_pos + match_len - 1] == c1)) break; + TDEFL_PROBE; TDEFL_PROBE; TDEFL_PROBE; + } + if (!dist) break; p = s; q = d->m_dict + probe_pos; for (probe_len = 0; probe_len < max_match_len; probe_len++) if (*p++ != *q++) break; + if (probe_len > match_len) + { + *pMatch_dist = dist; if ((*pMatch_len = match_len = probe_len) == max_match_len) return; + c0 = d->m_dict[pos + match_len]; c1 = d->m_dict[pos + match_len - 1]; + } + } +} +#endif // #if MINIZ_USE_UNALIGNED_LOADS_AND_STORES + +#if MINIZ_USE_UNALIGNED_LOADS_AND_STORES && MINIZ_LITTLE_ENDIAN +static mz_bool tdefl_compress_fast(tdefl_compressor *d) +{ + // Faster, minimally featured LZRW1-style match+parse loop with better register utilization. Intended for applications where raw throughput is valued more highly than ratio. + mz_uint lookahead_pos = d->m_lookahead_pos, lookahead_size = d->m_lookahead_size, dict_size = d->m_dict_size, total_lz_bytes = d->m_total_lz_bytes, num_flags_left = d->m_num_flags_left; + mz_uint8 *pLZ_code_buf = d->m_pLZ_code_buf, *pLZ_flags = d->m_pLZ_flags; + mz_uint cur_pos = lookahead_pos & TDEFL_LZ_DICT_SIZE_MASK; + + while ((d->m_src_buf_left) || ((d->m_flush) && (lookahead_size))) + { + const mz_uint TDEFL_COMP_FAST_LOOKAHEAD_SIZE = 4096; + mz_uint dst_pos = (lookahead_pos + lookahead_size) & TDEFL_LZ_DICT_SIZE_MASK; + mz_uint num_bytes_to_process = (mz_uint)MZ_MIN(d->m_src_buf_left, TDEFL_COMP_FAST_LOOKAHEAD_SIZE - lookahead_size); + d->m_src_buf_left -= num_bytes_to_process; + lookahead_size += num_bytes_to_process; + + while (num_bytes_to_process) + { + mz_uint32 n = MZ_MIN(TDEFL_LZ_DICT_SIZE - dst_pos, num_bytes_to_process); + memcpy(d->m_dict + dst_pos, d->m_pSrc, n); + if (dst_pos < (TDEFL_MAX_MATCH_LEN - 1)) + memcpy(d->m_dict + TDEFL_LZ_DICT_SIZE + dst_pos, d->m_pSrc, MZ_MIN(n, (TDEFL_MAX_MATCH_LEN - 1) - dst_pos)); + d->m_pSrc += n; + dst_pos = (dst_pos + n) & TDEFL_LZ_DICT_SIZE_MASK; + num_bytes_to_process -= n; + } + + dict_size = MZ_MIN(TDEFL_LZ_DICT_SIZE - lookahead_size, dict_size); + if ((!d->m_flush) && (lookahead_size < TDEFL_COMP_FAST_LOOKAHEAD_SIZE)) break; + + while (lookahead_size >= 4) + { + mz_uint cur_match_dist, cur_match_len = 1; + mz_uint8 *pCur_dict = d->m_dict + cur_pos; + mz_uint first_trigram = (*(const mz_uint32 *)pCur_dict) & 0xFFFFFF; + mz_uint hash = (first_trigram ^ (first_trigram >> (24 - (TDEFL_LZ_HASH_BITS - 8)))) & TDEFL_LEVEL1_HASH_SIZE_MASK; + mz_uint probe_pos = d->m_hash[hash]; + d->m_hash[hash] = (mz_uint16)lookahead_pos; + + if (((cur_match_dist = (mz_uint16)(lookahead_pos - probe_pos)) <= dict_size) && ((*(const mz_uint32 *)(d->m_dict + (probe_pos &= TDEFL_LZ_DICT_SIZE_MASK)) & 0xFFFFFF) == first_trigram)) + { + const mz_uint16 *p = (const mz_uint16 *)pCur_dict; + const mz_uint16 *q = (const mz_uint16 *)(d->m_dict + probe_pos); + mz_uint32 probe_len = 32; + do { } while ( (TDEFL_READ_UNALIGNED_WORD(++p) == TDEFL_READ_UNALIGNED_WORD(++q)) && (TDEFL_READ_UNALIGNED_WORD(++p) == TDEFL_READ_UNALIGNED_WORD(++q)) && + (TDEFL_READ_UNALIGNED_WORD(++p) == TDEFL_READ_UNALIGNED_WORD(++q)) && (TDEFL_READ_UNALIGNED_WORD(++p) == TDEFL_READ_UNALIGNED_WORD(++q)) && (--probe_len > 0) ); + cur_match_len = ((mz_uint)(p - (const mz_uint16 *)pCur_dict) * 2) + (mz_uint)(*(const mz_uint8 *)p == *(const mz_uint8 *)q); + if (!probe_len) + cur_match_len = cur_match_dist ? TDEFL_MAX_MATCH_LEN : 0; + + if ((cur_match_len < TDEFL_MIN_MATCH_LEN) || ((cur_match_len == TDEFL_MIN_MATCH_LEN) && (cur_match_dist >= 8U*1024U))) + { + cur_match_len = 1; + *pLZ_code_buf++ = (mz_uint8)first_trigram; + *pLZ_flags = (mz_uint8)(*pLZ_flags >> 1); + d->m_huff_count[0][(mz_uint8)first_trigram]++; + } + else + { + mz_uint32 s0, s1; + cur_match_len = MZ_MIN(cur_match_len, lookahead_size); + + MZ_ASSERT((cur_match_len >= TDEFL_MIN_MATCH_LEN) && (cur_match_dist >= 1) && (cur_match_dist <= TDEFL_LZ_DICT_SIZE)); + + cur_match_dist--; + + pLZ_code_buf[0] = (mz_uint8)(cur_match_len - TDEFL_MIN_MATCH_LEN); + *(mz_uint16 *)(&pLZ_code_buf[1]) = (mz_uint16)cur_match_dist; + pLZ_code_buf += 3; + *pLZ_flags = (mz_uint8)((*pLZ_flags >> 1) | 0x80); + + s0 = s_tdefl_small_dist_sym[cur_match_dist & 511]; + s1 = s_tdefl_large_dist_sym[cur_match_dist >> 8]; + d->m_huff_count[1][(cur_match_dist < 512) ? s0 : s1]++; + + d->m_huff_count[0][s_tdefl_len_sym[cur_match_len - TDEFL_MIN_MATCH_LEN]]++; + } + } + else + { + *pLZ_code_buf++ = (mz_uint8)first_trigram; + *pLZ_flags = (mz_uint8)(*pLZ_flags >> 1); + d->m_huff_count[0][(mz_uint8)first_trigram]++; + } + + if (--num_flags_left == 0) { num_flags_left = 8; pLZ_flags = pLZ_code_buf++; } + + total_lz_bytes += cur_match_len; + lookahead_pos += cur_match_len; + dict_size = MZ_MIN(dict_size + cur_match_len, TDEFL_LZ_DICT_SIZE); + cur_pos = (cur_pos + cur_match_len) & TDEFL_LZ_DICT_SIZE_MASK; + MZ_ASSERT(lookahead_size >= cur_match_len); + lookahead_size -= cur_match_len; + + if (pLZ_code_buf > &d->m_lz_code_buf[TDEFL_LZ_CODE_BUF_SIZE - 8]) + { + int n; + d->m_lookahead_pos = lookahead_pos; d->m_lookahead_size = lookahead_size; d->m_dict_size = dict_size; + d->m_total_lz_bytes = total_lz_bytes; d->m_pLZ_code_buf = pLZ_code_buf; d->m_pLZ_flags = pLZ_flags; d->m_num_flags_left = num_flags_left; + if ((n = tdefl_flush_block(d, 0)) != 0) + return (n < 0) ? MZ_FALSE : MZ_TRUE; + total_lz_bytes = d->m_total_lz_bytes; pLZ_code_buf = d->m_pLZ_code_buf; pLZ_flags = d->m_pLZ_flags; num_flags_left = d->m_num_flags_left; + } + } + + while (lookahead_size) + { + mz_uint8 lit = d->m_dict[cur_pos]; + + total_lz_bytes++; + *pLZ_code_buf++ = lit; + *pLZ_flags = (mz_uint8)(*pLZ_flags >> 1); + if (--num_flags_left == 0) { num_flags_left = 8; pLZ_flags = pLZ_code_buf++; } + + d->m_huff_count[0][lit]++; + + lookahead_pos++; + dict_size = MZ_MIN(dict_size + 1, TDEFL_LZ_DICT_SIZE); + cur_pos = (cur_pos + 1) & TDEFL_LZ_DICT_SIZE_MASK; + lookahead_size--; + + if (pLZ_code_buf > &d->m_lz_code_buf[TDEFL_LZ_CODE_BUF_SIZE - 8]) + { + int n; + d->m_lookahead_pos = lookahead_pos; d->m_lookahead_size = lookahead_size; d->m_dict_size = dict_size; + d->m_total_lz_bytes = total_lz_bytes; d->m_pLZ_code_buf = pLZ_code_buf; d->m_pLZ_flags = pLZ_flags; d->m_num_flags_left = num_flags_left; + if ((n = tdefl_flush_block(d, 0)) != 0) + return (n < 0) ? MZ_FALSE : MZ_TRUE; + total_lz_bytes = d->m_total_lz_bytes; pLZ_code_buf = d->m_pLZ_code_buf; pLZ_flags = d->m_pLZ_flags; num_flags_left = d->m_num_flags_left; + } + } + } + + d->m_lookahead_pos = lookahead_pos; d->m_lookahead_size = lookahead_size; d->m_dict_size = dict_size; + d->m_total_lz_bytes = total_lz_bytes; d->m_pLZ_code_buf = pLZ_code_buf; d->m_pLZ_flags = pLZ_flags; d->m_num_flags_left = num_flags_left; + return MZ_TRUE; +} +#endif // MINIZ_USE_UNALIGNED_LOADS_AND_STORES && MINIZ_LITTLE_ENDIAN + +static MZ_FORCEINLINE void tdefl_record_literal(tdefl_compressor *d, mz_uint8 lit) +{ + d->m_total_lz_bytes++; + *d->m_pLZ_code_buf++ = lit; + *d->m_pLZ_flags = (mz_uint8)(*d->m_pLZ_flags >> 1); if (--d->m_num_flags_left == 0) { d->m_num_flags_left = 8; d->m_pLZ_flags = d->m_pLZ_code_buf++; } + d->m_huff_count[0][lit]++; +} + +static MZ_FORCEINLINE void tdefl_record_match(tdefl_compressor *d, mz_uint match_len, mz_uint match_dist) +{ + mz_uint32 s0, s1; + + MZ_ASSERT((match_len >= TDEFL_MIN_MATCH_LEN) && (match_dist >= 1) && (match_dist <= TDEFL_LZ_DICT_SIZE)); + + d->m_total_lz_bytes += match_len; + + d->m_pLZ_code_buf[0] = (mz_uint8)(match_len - TDEFL_MIN_MATCH_LEN); + + match_dist -= 1; + d->m_pLZ_code_buf[1] = (mz_uint8)(match_dist & 0xFF); + d->m_pLZ_code_buf[2] = (mz_uint8)(match_dist >> 8); d->m_pLZ_code_buf += 3; + + *d->m_pLZ_flags = (mz_uint8)((*d->m_pLZ_flags >> 1) | 0x80); if (--d->m_num_flags_left == 0) { d->m_num_flags_left = 8; d->m_pLZ_flags = d->m_pLZ_code_buf++; } + + s0 = s_tdefl_small_dist_sym[match_dist & 511]; s1 = s_tdefl_large_dist_sym[(match_dist >> 8) & 127]; + d->m_huff_count[1][(match_dist < 512) ? s0 : s1]++; + + if (match_len >= TDEFL_MIN_MATCH_LEN) d->m_huff_count[0][s_tdefl_len_sym[match_len - TDEFL_MIN_MATCH_LEN]]++; +} + +static mz_bool tdefl_compress_normal(tdefl_compressor *d) +{ + const mz_uint8 *pSrc = d->m_pSrc; size_t src_buf_left = d->m_src_buf_left; + tdefl_flush flush = d->m_flush; + + while ((src_buf_left) || ((flush) && (d->m_lookahead_size))) + { + mz_uint len_to_move, cur_match_dist, cur_match_len, cur_pos; + // Update dictionary and hash chains. Keeps the lookahead size equal to TDEFL_MAX_MATCH_LEN. + if ((d->m_lookahead_size + d->m_dict_size) >= (TDEFL_MIN_MATCH_LEN - 1)) + { + mz_uint dst_pos = (d->m_lookahead_pos + d->m_lookahead_size) & TDEFL_LZ_DICT_SIZE_MASK, ins_pos = d->m_lookahead_pos + d->m_lookahead_size - 2; + mz_uint hash = (d->m_dict[ins_pos & TDEFL_LZ_DICT_SIZE_MASK] << TDEFL_LZ_HASH_SHIFT) ^ d->m_dict[(ins_pos + 1) & TDEFL_LZ_DICT_SIZE_MASK]; + mz_uint num_bytes_to_process = (mz_uint)MZ_MIN(src_buf_left, TDEFL_MAX_MATCH_LEN - d->m_lookahead_size); + const mz_uint8 *pSrc_end = pSrc + num_bytes_to_process; + src_buf_left -= num_bytes_to_process; + d->m_lookahead_size += num_bytes_to_process; + while (pSrc != pSrc_end) + { + mz_uint8 c = *pSrc++; d->m_dict[dst_pos] = c; if (dst_pos < (TDEFL_MAX_MATCH_LEN - 1)) d->m_dict[TDEFL_LZ_DICT_SIZE + dst_pos] = c; + hash = ((hash << TDEFL_LZ_HASH_SHIFT) ^ c) & (TDEFL_LZ_HASH_SIZE - 1); + d->m_next[ins_pos & TDEFL_LZ_DICT_SIZE_MASK] = d->m_hash[hash]; d->m_hash[hash] = (mz_uint16)(ins_pos); + dst_pos = (dst_pos + 1) & TDEFL_LZ_DICT_SIZE_MASK; ins_pos++; + } + } + else + { + while ((src_buf_left) && (d->m_lookahead_size < TDEFL_MAX_MATCH_LEN)) + { + mz_uint8 c = *pSrc++; + mz_uint dst_pos = (d->m_lookahead_pos + d->m_lookahead_size) & TDEFL_LZ_DICT_SIZE_MASK; + src_buf_left--; + d->m_dict[dst_pos] = c; + if (dst_pos < (TDEFL_MAX_MATCH_LEN - 1)) + d->m_dict[TDEFL_LZ_DICT_SIZE + dst_pos] = c; + if ((++d->m_lookahead_size + d->m_dict_size) >= TDEFL_MIN_MATCH_LEN) + { + mz_uint ins_pos = d->m_lookahead_pos + (d->m_lookahead_size - 1) - 2; + mz_uint hash = ((d->m_dict[ins_pos & TDEFL_LZ_DICT_SIZE_MASK] << (TDEFL_LZ_HASH_SHIFT * 2)) ^ (d->m_dict[(ins_pos + 1) & TDEFL_LZ_DICT_SIZE_MASK] << TDEFL_LZ_HASH_SHIFT) ^ c) & (TDEFL_LZ_HASH_SIZE - 1); + d->m_next[ins_pos & TDEFL_LZ_DICT_SIZE_MASK] = d->m_hash[hash]; d->m_hash[hash] = (mz_uint16)(ins_pos); + } + } + } + d->m_dict_size = MZ_MIN(TDEFL_LZ_DICT_SIZE - d->m_lookahead_size, d->m_dict_size); + if ((!flush) && (d->m_lookahead_size < TDEFL_MAX_MATCH_LEN)) + break; + + // Simple lazy/greedy parsing state machine. + len_to_move = 1; cur_match_dist = 0; cur_match_len = d->m_saved_match_len ? d->m_saved_match_len : (TDEFL_MIN_MATCH_LEN - 1); cur_pos = d->m_lookahead_pos & TDEFL_LZ_DICT_SIZE_MASK; + if (d->m_flags & (TDEFL_RLE_MATCHES | TDEFL_FORCE_ALL_RAW_BLOCKS)) + { + if ((d->m_dict_size) && (!(d->m_flags & TDEFL_FORCE_ALL_RAW_BLOCKS))) + { + mz_uint8 c = d->m_dict[(cur_pos - 1) & TDEFL_LZ_DICT_SIZE_MASK]; + cur_match_len = 0; while (cur_match_len < d->m_lookahead_size) { if (d->m_dict[cur_pos + cur_match_len] != c) break; cur_match_len++; } + if (cur_match_len < TDEFL_MIN_MATCH_LEN) cur_match_len = 0; else cur_match_dist = 1; + } + } + else + { + tdefl_find_match(d, d->m_lookahead_pos, d->m_dict_size, d->m_lookahead_size, &cur_match_dist, &cur_match_len); + } + if (((cur_match_len == TDEFL_MIN_MATCH_LEN) && (cur_match_dist >= 8U*1024U)) || (cur_pos == cur_match_dist) || ((d->m_flags & TDEFL_FILTER_MATCHES) && (cur_match_len <= 5))) + { + cur_match_dist = cur_match_len = 0; + } + if (d->m_saved_match_len) + { + if (cur_match_len > d->m_saved_match_len) + { + tdefl_record_literal(d, (mz_uint8)d->m_saved_lit); + if (cur_match_len >= 128) + { + tdefl_record_match(d, cur_match_len, cur_match_dist); + d->m_saved_match_len = 0; len_to_move = cur_match_len; + } + else + { + d->m_saved_lit = d->m_dict[cur_pos]; d->m_saved_match_dist = cur_match_dist; d->m_saved_match_len = cur_match_len; + } + } + else + { + tdefl_record_match(d, d->m_saved_match_len, d->m_saved_match_dist); + len_to_move = d->m_saved_match_len - 1; d->m_saved_match_len = 0; + } + } + else if (!cur_match_dist) + tdefl_record_literal(d, d->m_dict[MZ_MIN(cur_pos, sizeof(d->m_dict) - 1)]); + else if ((d->m_greedy_parsing) || (d->m_flags & TDEFL_RLE_MATCHES) || (cur_match_len >= 128)) + { + tdefl_record_match(d, cur_match_len, cur_match_dist); + len_to_move = cur_match_len; + } + else + { + d->m_saved_lit = d->m_dict[MZ_MIN(cur_pos, sizeof(d->m_dict) - 1)]; d->m_saved_match_dist = cur_match_dist; d->m_saved_match_len = cur_match_len; + } + // Move the lookahead forward by len_to_move bytes. + d->m_lookahead_pos += len_to_move; + MZ_ASSERT(d->m_lookahead_size >= len_to_move); + d->m_lookahead_size -= len_to_move; + d->m_dict_size = MZ_MIN(d->m_dict_size + len_to_move, TDEFL_LZ_DICT_SIZE); + // Check if it's time to flush the current LZ codes to the internal output buffer. + if ( (d->m_pLZ_code_buf > &d->m_lz_code_buf[TDEFL_LZ_CODE_BUF_SIZE - 8]) || + ( (d->m_total_lz_bytes > 31*1024) && (((((mz_uint)(d->m_pLZ_code_buf - d->m_lz_code_buf) * 115) >> 7) >= d->m_total_lz_bytes) || (d->m_flags & TDEFL_FORCE_ALL_RAW_BLOCKS))) ) + { + int n; + d->m_pSrc = pSrc; d->m_src_buf_left = src_buf_left; + if ((n = tdefl_flush_block(d, 0)) != 0) + return (n < 0) ? MZ_FALSE : MZ_TRUE; + } + } + + d->m_pSrc = pSrc; d->m_src_buf_left = src_buf_left; + return MZ_TRUE; +} + +static tdefl_status tdefl_flush_output_buffer(tdefl_compressor *d) +{ + if (d->m_pIn_buf_size) + { + *d->m_pIn_buf_size = d->m_pSrc - (const mz_uint8 *)d->m_pIn_buf; + } + + if (d->m_pOut_buf_size) + { + size_t n = MZ_MIN(*d->m_pOut_buf_size - d->m_out_buf_ofs, d->m_output_flush_remaining); + memcpy((mz_uint8 *)d->m_pOut_buf + d->m_out_buf_ofs, d->m_output_buf + d->m_output_flush_ofs, n); + d->m_output_flush_ofs += (mz_uint)n; + d->m_output_flush_remaining -= (mz_uint)n; + d->m_out_buf_ofs += n; + + *d->m_pOut_buf_size = d->m_out_buf_ofs; + } + + return (d->m_finished && !d->m_output_flush_remaining) ? TDEFL_STATUS_DONE : TDEFL_STATUS_OKAY; +} + +tdefl_status tdefl_compress(tdefl_compressor *d, const void *pIn_buf, size_t *pIn_buf_size, void *pOut_buf, size_t *pOut_buf_size, tdefl_flush flush) +{ + if (!d) + { + if (pIn_buf_size) *pIn_buf_size = 0; + if (pOut_buf_size) *pOut_buf_size = 0; + return TDEFL_STATUS_BAD_PARAM; + } + + d->m_pIn_buf = pIn_buf; d->m_pIn_buf_size = pIn_buf_size; + d->m_pOut_buf = pOut_buf; d->m_pOut_buf_size = pOut_buf_size; + d->m_pSrc = (const mz_uint8 *)(pIn_buf); d->m_src_buf_left = pIn_buf_size ? *pIn_buf_size : 0; + d->m_out_buf_ofs = 0; + d->m_flush = flush; + + if ( ((d->m_pPut_buf_func != NULL) == ((pOut_buf != NULL) || (pOut_buf_size != NULL))) || (d->m_prev_return_status != TDEFL_STATUS_OKAY) || + (d->m_wants_to_finish && (flush != TDEFL_FINISH)) || (pIn_buf_size && *pIn_buf_size && !pIn_buf) || (pOut_buf_size && *pOut_buf_size && !pOut_buf) ) + { + if (pIn_buf_size) *pIn_buf_size = 0; + if (pOut_buf_size) *pOut_buf_size = 0; + return (d->m_prev_return_status = TDEFL_STATUS_BAD_PARAM); + } + d->m_wants_to_finish |= (flush == TDEFL_FINISH); + + if ((d->m_output_flush_remaining) || (d->m_finished)) + return (d->m_prev_return_status = tdefl_flush_output_buffer(d)); + +#if MINIZ_USE_UNALIGNED_LOADS_AND_STORES && MINIZ_LITTLE_ENDIAN + if (((d->m_flags & TDEFL_MAX_PROBES_MASK) == 1) && + ((d->m_flags & TDEFL_GREEDY_PARSING_FLAG) != 0) && + ((d->m_flags & (TDEFL_FILTER_MATCHES | TDEFL_FORCE_ALL_RAW_BLOCKS | TDEFL_RLE_MATCHES)) == 0)) + { + if (!tdefl_compress_fast(d)) + return d->m_prev_return_status; + } + else +#endif // #if MINIZ_USE_UNALIGNED_LOADS_AND_STORES && MINIZ_LITTLE_ENDIAN + { + if (!tdefl_compress_normal(d)) + return d->m_prev_return_status; + } + + if ((d->m_flags & (TDEFL_WRITE_ZLIB_HEADER | TDEFL_COMPUTE_ADLER32)) && (pIn_buf)) + d->m_adler32 = (mz_uint32)mz_adler32(d->m_adler32, (const mz_uint8 *)pIn_buf, d->m_pSrc - (const mz_uint8 *)pIn_buf); + + if ((flush) && (!d->m_lookahead_size) && (!d->m_src_buf_left) && (!d->m_output_flush_remaining)) + { + if (tdefl_flush_block(d, flush) < 0) + return d->m_prev_return_status; + d->m_finished = (flush == TDEFL_FINISH); + if (flush == TDEFL_FULL_FLUSH) { MZ_CLEAR_OBJ(d->m_hash); MZ_CLEAR_OBJ(d->m_next); d->m_dict_size = 0; } + } + + return (d->m_prev_return_status = tdefl_flush_output_buffer(d)); +} + +tdefl_status tdefl_compress_buffer(tdefl_compressor *d, const void *pIn_buf, size_t in_buf_size, tdefl_flush flush) +{ + MZ_ASSERT(d->m_pPut_buf_func); return tdefl_compress(d, pIn_buf, &in_buf_size, NULL, NULL, flush); +} + +tdefl_status tdefl_init(tdefl_compressor *d, tdefl_put_buf_func_ptr pPut_buf_func, void *pPut_buf_user, int flags) +{ + d->m_pPut_buf_func = pPut_buf_func; d->m_pPut_buf_user = pPut_buf_user; + d->m_flags = (mz_uint)(flags); d->m_max_probes[0] = 1 + ((flags & 0xFFF) + 2) / 3; d->m_greedy_parsing = (flags & TDEFL_GREEDY_PARSING_FLAG) != 0; + d->m_max_probes[1] = 1 + (((flags & 0xFFF) >> 2) + 2) / 3; + if (!(flags & TDEFL_NONDETERMINISTIC_PARSING_FLAG)) MZ_CLEAR_OBJ(d->m_hash); + d->m_lookahead_pos = d->m_lookahead_size = d->m_dict_size = d->m_total_lz_bytes = d->m_lz_code_buf_dict_pos = d->m_bits_in = 0; + d->m_output_flush_ofs = d->m_output_flush_remaining = d->m_finished = d->m_block_index = d->m_bit_buffer = d->m_wants_to_finish = 0; + d->m_pLZ_code_buf = d->m_lz_code_buf + 1; d->m_pLZ_flags = d->m_lz_code_buf; d->m_num_flags_left = 8; + d->m_pOutput_buf = d->m_output_buf; d->m_pOutput_buf_end = d->m_output_buf; d->m_prev_return_status = TDEFL_STATUS_OKAY; + d->m_saved_match_dist = d->m_saved_match_len = d->m_saved_lit = 0; d->m_adler32 = 1; + d->m_pIn_buf = NULL; d->m_pOut_buf = NULL; + d->m_pIn_buf_size = NULL; d->m_pOut_buf_size = NULL; + d->m_flush = TDEFL_NO_FLUSH; d->m_pSrc = NULL; d->m_src_buf_left = 0; d->m_out_buf_ofs = 0; + memset(&d->m_huff_count[0][0], 0, sizeof(d->m_huff_count[0][0]) * TDEFL_MAX_HUFF_SYMBOLS_0); + memset(&d->m_huff_count[1][0], 0, sizeof(d->m_huff_count[1][0]) * TDEFL_MAX_HUFF_SYMBOLS_1); + return TDEFL_STATUS_OKAY; +} + +tdefl_status tdefl_get_prev_return_status(tdefl_compressor *d) +{ + return d->m_prev_return_status; +} + +mz_uint32 tdefl_get_adler32(tdefl_compressor *d) +{ + return d->m_adler32; +} + +mz_bool tdefl_compress_mem_to_output(const void *pBuf, size_t buf_len, tdefl_put_buf_func_ptr pPut_buf_func, void *pPut_buf_user, int flags) +{ + tdefl_compressor *pComp; mz_bool succeeded; if (((buf_len) && (!pBuf)) || (!pPut_buf_func)) return MZ_FALSE; + pComp = (tdefl_compressor*)MZ_MALLOC(sizeof(tdefl_compressor)); if (!pComp) return MZ_FALSE; + succeeded = (tdefl_init(pComp, pPut_buf_func, pPut_buf_user, flags) == TDEFL_STATUS_OKAY); + succeeded = succeeded && (tdefl_compress_buffer(pComp, pBuf, buf_len, TDEFL_FINISH) == TDEFL_STATUS_DONE); + MZ_FREE(pComp); return succeeded; +} + +typedef struct +{ + size_t m_size, m_capacity; + mz_uint8 *m_pBuf; + mz_bool m_expandable; +} tdefl_output_buffer; + +static mz_bool tdefl_output_buffer_putter(const void *pBuf, int len, void *pUser) +{ + tdefl_output_buffer *p = (tdefl_output_buffer *)pUser; + size_t new_size = p->m_size + len; + if (new_size > p->m_capacity) + { + size_t new_capacity = p->m_capacity; mz_uint8 *pNew_buf; if (!p->m_expandable) return MZ_FALSE; + do { new_capacity = MZ_MAX(128U, new_capacity << 1U); } while (new_size > new_capacity); + pNew_buf = (mz_uint8*)MZ_REALLOC(p->m_pBuf, new_capacity); if (!pNew_buf) return MZ_FALSE; + p->m_pBuf = pNew_buf; p->m_capacity = new_capacity; + } + memcpy((mz_uint8*)p->m_pBuf + p->m_size, pBuf, len); p->m_size = new_size; + return MZ_TRUE; +} + +void *tdefl_compress_mem_to_heap(const void *pSrc_buf, size_t src_buf_len, size_t *pOut_len, int flags) +{ + tdefl_output_buffer out_buf; MZ_CLEAR_OBJ(out_buf); + if (!pOut_len) return MZ_FALSE; else *pOut_len = 0; + out_buf.m_expandable = MZ_TRUE; + if (!tdefl_compress_mem_to_output(pSrc_buf, src_buf_len, tdefl_output_buffer_putter, &out_buf, flags)) return NULL; + *pOut_len = out_buf.m_size; return out_buf.m_pBuf; +} + +size_t tdefl_compress_mem_to_mem(void *pOut_buf, size_t out_buf_len, const void *pSrc_buf, size_t src_buf_len, int flags) +{ + tdefl_output_buffer out_buf; MZ_CLEAR_OBJ(out_buf); + if (!pOut_buf) return 0; + out_buf.m_pBuf = (mz_uint8*)pOut_buf; out_buf.m_capacity = out_buf_len; + if (!tdefl_compress_mem_to_output(pSrc_buf, src_buf_len, tdefl_output_buffer_putter, &out_buf, flags)) return 0; + return out_buf.m_size; +} + +#ifndef MINIZ_NO_ZLIB_APIS +static const mz_uint s_tdefl_num_probes[11] = { 0, 1, 6, 32, 16, 32, 128, 256, 512, 768, 1500 }; + +// level may actually range from [0,10] (10 is a "hidden" max level, where we want a bit more compression and it's fine if throughput to fall off a cliff on some files). +mz_uint tdefl_create_comp_flags_from_zip_params(int level, int window_bits, int strategy) +{ + mz_uint comp_flags = s_tdefl_num_probes[(level >= 0) ? MZ_MIN(10, level) : MZ_DEFAULT_LEVEL] | ((level <= 3) ? TDEFL_GREEDY_PARSING_FLAG : 0); + if (window_bits > 0) comp_flags |= TDEFL_WRITE_ZLIB_HEADER; + + if (!level) comp_flags |= TDEFL_FORCE_ALL_RAW_BLOCKS; + else if (strategy == MZ_FILTERED) comp_flags |= TDEFL_FILTER_MATCHES; + else if (strategy == MZ_HUFFMAN_ONLY) comp_flags &= ~TDEFL_MAX_PROBES_MASK; + else if (strategy == MZ_FIXED) comp_flags |= TDEFL_FORCE_ALL_STATIC_BLOCKS; + else if (strategy == MZ_RLE) comp_flags |= TDEFL_RLE_MATCHES; + + return comp_flags; +} +#endif //MINIZ_NO_ZLIB_APIS + +#ifdef _MSC_VER +#pragma warning (push) +#pragma warning (disable:4204) // nonstandard extension used : non-constant aggregate initializer (also supported by GNU C and C99, so no big deal) +#endif + +// Simple PNG writer function by Alex Evans, 2011. Released into the public domain: https://gist.github.com/908299, more context at +// http://altdevblogaday.org/2011/04/06/a-smaller-jpg-encoder/. +// This is actually a modification of Alex's original code so PNG files generated by this function pass pngcheck. +void *tdefl_write_image_to_png_file_in_memory_ex(const void *pImage, int w, int h, int num_chans, size_t *pLen_out, mz_uint level, mz_bool flip) +{ + // Using a local copy of this array here in case MINIZ_NO_ZLIB_APIS was defined. + static const mz_uint s_tdefl_png_num_probes[11] = { 0, 1, 6, 32, 16, 32, 128, 256, 512, 768, 1500 }; + tdefl_compressor *pComp = (tdefl_compressor *)MZ_MALLOC(sizeof(tdefl_compressor)); tdefl_output_buffer out_buf; int i, bpl = w * num_chans, y, z; mz_uint32 c; *pLen_out = 0; + if (!pComp) return NULL; + MZ_CLEAR_OBJ(out_buf); out_buf.m_expandable = MZ_TRUE; out_buf.m_capacity = 57+MZ_MAX(64, (1+bpl)*h); if (NULL == (out_buf.m_pBuf = (mz_uint8*)MZ_MALLOC(out_buf.m_capacity))) { MZ_FREE(pComp); return NULL; } + // write dummy header + for (z = 41; z; --z) tdefl_output_buffer_putter(&z, 1, &out_buf); + // compress image data + tdefl_init(pComp, tdefl_output_buffer_putter, &out_buf, s_tdefl_png_num_probes[MZ_MIN(10, level)] | TDEFL_WRITE_ZLIB_HEADER); + for (y = 0; y < h; ++y) { tdefl_compress_buffer(pComp, &z, 1, TDEFL_NO_FLUSH); tdefl_compress_buffer(pComp, (mz_uint8*)pImage + (flip ? (h - 1 - y) : y) * bpl, bpl, TDEFL_NO_FLUSH); } + if (tdefl_compress_buffer(pComp, NULL, 0, TDEFL_FINISH) != TDEFL_STATUS_DONE) { MZ_FREE(pComp); MZ_FREE(out_buf.m_pBuf); return NULL; } + // write real header + *pLen_out = out_buf.m_size-41; + { + static const mz_uint8 chans[] = {0x00, 0x00, 0x04, 0x02, 0x06}; + mz_uint8 pnghdr[41]={0x89,0x50,0x4e,0x47,0x0d,0x0a,0x1a,0x0a,0x00,0x00,0x00,0x0d,0x49,0x48,0x44,0x52, + 0,0,(mz_uint8)(w>>8),(mz_uint8)w,0,0,(mz_uint8)(h>>8),(mz_uint8)h,8,chans[num_chans],0,0,0,0,0,0,0, + (mz_uint8)(*pLen_out>>24),(mz_uint8)(*pLen_out>>16),(mz_uint8)(*pLen_out>>8),(mz_uint8)*pLen_out,0x49,0x44,0x41,0x54}; + c=(mz_uint32)mz_crc32(MZ_CRC32_INIT,pnghdr+12,17); for (i=0; i<4; ++i, c<<=8) ((mz_uint8*)(pnghdr+29))[i]=(mz_uint8)(c>>24); + memcpy(out_buf.m_pBuf, pnghdr, 41); + } + // write footer (IDAT CRC-32, followed by IEND chunk) + if (!tdefl_output_buffer_putter("\0\0\0\0\0\0\0\0\x49\x45\x4e\x44\xae\x42\x60\x82", 16, &out_buf)) { *pLen_out = 0; MZ_FREE(pComp); MZ_FREE(out_buf.m_pBuf); return NULL; } + c = (mz_uint32)mz_crc32(MZ_CRC32_INIT,out_buf.m_pBuf+41-4, *pLen_out+4); for (i=0; i<4; ++i, c<<=8) (out_buf.m_pBuf+out_buf.m_size-16)[i] = (mz_uint8)(c >> 24); + // compute final size of file, grab compressed data buffer and return + *pLen_out += 57; MZ_FREE(pComp); return out_buf.m_pBuf; +} +void *tdefl_write_image_to_png_file_in_memory(const void *pImage, int w, int h, int num_chans, size_t *pLen_out) +{ + // Level 6 corresponds to TDEFL_DEFAULT_MAX_PROBES or MZ_DEFAULT_LEVEL (but we can't depend on MZ_DEFAULT_LEVEL being available in case the zlib API's where #defined out) + return tdefl_write_image_to_png_file_in_memory_ex(pImage, w, h, num_chans, pLen_out, 6, MZ_FALSE); +} + +#ifdef _MSC_VER +#pragma warning (pop) +#endif + +// ------------------- .ZIP archive reading + +#ifndef MINIZ_NO_ARCHIVE_APIS + +#ifdef MINIZ_NO_STDIO + #define MZ_FILE void * +#else + #include + #include + + #if defined(_MSC_VER) || defined(__MINGW64__) + static FILE *mz_fopen(const char *pFilename, const char *pMode) + { + FILE* pFile = NULL; + fopen_s(&pFile, pFilename, pMode); + return pFile; + } + static FILE *mz_freopen(const char *pPath, const char *pMode, FILE *pStream) + { + FILE* pFile = NULL; + if (freopen_s(&pFile, pPath, pMode, pStream)) + return NULL; + return pFile; + } + #ifndef MINIZ_NO_TIME + #include + #endif + #define MZ_FILE FILE + #define MZ_FOPEN mz_fopen + #define MZ_FCLOSE fclose + #define MZ_FREAD fread + #define MZ_FWRITE fwrite + #define MZ_FTELL64 _ftelli64 + #define MZ_FSEEK64 _fseeki64 + #define MZ_FILE_STAT_STRUCT _stat + #define MZ_FILE_STAT _stat + #define MZ_FFLUSH fflush + #define MZ_FREOPEN mz_freopen + #define MZ_DELETE_FILE remove + #elif defined(__MINGW32__) + #ifndef MINIZ_NO_TIME + #include + #endif + #define MZ_FILE FILE + #define MZ_FOPEN(f, m) fopen(f, m) + #define MZ_FCLOSE fclose + #define MZ_FREAD fread + #define MZ_FWRITE fwrite + #define MZ_FTELL64 ftello64 + #define MZ_FSEEK64 fseeko64 + #define MZ_FILE_STAT_STRUCT _stat + #define MZ_FILE_STAT _stat + #define MZ_FFLUSH fflush + #define MZ_FREOPEN(f, m, s) freopen(f, m, s) + #define MZ_DELETE_FILE remove + #elif defined(__TINYC__) + #ifndef MINIZ_NO_TIME + #include + #endif + #define MZ_FILE FILE + #define MZ_FOPEN(f, m) fopen(f, m) + #define MZ_FCLOSE fclose + #define MZ_FREAD fread + #define MZ_FWRITE fwrite + #define MZ_FTELL64 ftell + #define MZ_FSEEK64 fseek + #define MZ_FILE_STAT_STRUCT stat + #define MZ_FILE_STAT stat + #define MZ_FFLUSH fflush + #define MZ_FREOPEN(f, m, s) freopen(f, m, s) + #define MZ_DELETE_FILE remove + #elif defined(__GNUC__) && _LARGEFILE64_SOURCE + #ifndef MINIZ_NO_TIME + #include + #endif + #define MZ_FILE FILE + #define MZ_FOPEN(f, m) fopen64(f, m) + #define MZ_FCLOSE fclose + #define MZ_FREAD fread + #define MZ_FWRITE fwrite + #define MZ_FTELL64 ftello64 + #define MZ_FSEEK64 fseeko64 + #define MZ_FILE_STAT_STRUCT stat64 + #define MZ_FILE_STAT stat64 + #define MZ_FFLUSH fflush + #define MZ_FREOPEN(p, m, s) freopen64(p, m, s) + #define MZ_DELETE_FILE remove + #else + #ifndef MINIZ_NO_TIME + #include + #endif + #define MZ_FILE FILE + #define MZ_FOPEN(f, m) fopen(f, m) + #define MZ_FCLOSE fclose + #define MZ_FREAD fread + #define MZ_FWRITE fwrite + #define MZ_FTELL64 ftello + #define MZ_FSEEK64 fseeko + #define MZ_FILE_STAT_STRUCT stat + #define MZ_FILE_STAT stat + #define MZ_FFLUSH fflush + #define MZ_FREOPEN(f, m, s) freopen(f, m, s) + #define MZ_DELETE_FILE remove + #endif // #ifdef _MSC_VER +#endif // #ifdef MINIZ_NO_STDIO + +#define MZ_TOLOWER(c) ((((c) >= 'A') && ((c) <= 'Z')) ? ((c) - 'A' + 'a') : (c)) + +// Various ZIP archive enums. To completely avoid cross platform compiler alignment and platform endian issues, miniz.c doesn't use structs for any of this stuff. +enum +{ + // ZIP archive identifiers and record sizes + MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIG = 0x06054b50, MZ_ZIP_CENTRAL_DIR_HEADER_SIG = 0x02014b50, MZ_ZIP_LOCAL_DIR_HEADER_SIG = 0x04034b50, + MZ_ZIP_LOCAL_DIR_HEADER_SIZE = 30, MZ_ZIP_CENTRAL_DIR_HEADER_SIZE = 46, MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE = 22, + // Central directory header record offsets + MZ_ZIP_CDH_SIG_OFS = 0, MZ_ZIP_CDH_VERSION_MADE_BY_OFS = 4, MZ_ZIP_CDH_VERSION_NEEDED_OFS = 6, MZ_ZIP_CDH_BIT_FLAG_OFS = 8, + MZ_ZIP_CDH_METHOD_OFS = 10, MZ_ZIP_CDH_FILE_TIME_OFS = 12, MZ_ZIP_CDH_FILE_DATE_OFS = 14, MZ_ZIP_CDH_CRC32_OFS = 16, + MZ_ZIP_CDH_COMPRESSED_SIZE_OFS = 20, MZ_ZIP_CDH_DECOMPRESSED_SIZE_OFS = 24, MZ_ZIP_CDH_FILENAME_LEN_OFS = 28, MZ_ZIP_CDH_EXTRA_LEN_OFS = 30, + MZ_ZIP_CDH_COMMENT_LEN_OFS = 32, MZ_ZIP_CDH_DISK_START_OFS = 34, MZ_ZIP_CDH_INTERNAL_ATTR_OFS = 36, MZ_ZIP_CDH_EXTERNAL_ATTR_OFS = 38, MZ_ZIP_CDH_LOCAL_HEADER_OFS = 42, + // Local directory header offsets + MZ_ZIP_LDH_SIG_OFS = 0, MZ_ZIP_LDH_VERSION_NEEDED_OFS = 4, MZ_ZIP_LDH_BIT_FLAG_OFS = 6, MZ_ZIP_LDH_METHOD_OFS = 8, MZ_ZIP_LDH_FILE_TIME_OFS = 10, + MZ_ZIP_LDH_FILE_DATE_OFS = 12, MZ_ZIP_LDH_CRC32_OFS = 14, MZ_ZIP_LDH_COMPRESSED_SIZE_OFS = 18, MZ_ZIP_LDH_DECOMPRESSED_SIZE_OFS = 22, + MZ_ZIP_LDH_FILENAME_LEN_OFS = 26, MZ_ZIP_LDH_EXTRA_LEN_OFS = 28, + // End of central directory offsets + MZ_ZIP_ECDH_SIG_OFS = 0, MZ_ZIP_ECDH_NUM_THIS_DISK_OFS = 4, MZ_ZIP_ECDH_NUM_DISK_CDIR_OFS = 6, MZ_ZIP_ECDH_CDIR_NUM_ENTRIES_ON_DISK_OFS = 8, + MZ_ZIP_ECDH_CDIR_TOTAL_ENTRIES_OFS = 10, MZ_ZIP_ECDH_CDIR_SIZE_OFS = 12, MZ_ZIP_ECDH_CDIR_OFS_OFS = 16, MZ_ZIP_ECDH_COMMENT_SIZE_OFS = 20, +}; + +typedef struct +{ + void *m_p; + size_t m_size, m_capacity; + mz_uint m_element_size; +} mz_zip_array; + +struct mz_zip_internal_state_tag +{ + mz_zip_array m_central_dir; + mz_zip_array m_central_dir_offsets; + mz_zip_array m_sorted_central_dir_offsets; + MZ_FILE *m_pFile; + void *m_pMem; + size_t m_mem_size; + size_t m_mem_capacity; +}; + +#define MZ_ZIP_ARRAY_SET_ELEMENT_SIZE(array_ptr, element_size) (array_ptr)->m_element_size = element_size +#define MZ_ZIP_ARRAY_ELEMENT(array_ptr, element_type, index) ((element_type *)((array_ptr)->m_p))[index] + +static MZ_FORCEINLINE void mz_zip_array_clear(mz_zip_archive *pZip, mz_zip_array *pArray) +{ + pZip->m_pFree(pZip->m_pAlloc_opaque, pArray->m_p); + memset(pArray, 0, sizeof(mz_zip_array)); +} + +static mz_bool mz_zip_array_ensure_capacity(mz_zip_archive *pZip, mz_zip_array *pArray, size_t min_new_capacity, mz_uint growing) +{ + void *pNew_p; size_t new_capacity = min_new_capacity; MZ_ASSERT(pArray->m_element_size); if (pArray->m_capacity >= min_new_capacity) return MZ_TRUE; + if (growing) { new_capacity = MZ_MAX(1, pArray->m_capacity); while (new_capacity < min_new_capacity) new_capacity *= 2; } + if (NULL == (pNew_p = pZip->m_pRealloc(pZip->m_pAlloc_opaque, pArray->m_p, pArray->m_element_size, new_capacity))) return MZ_FALSE; + pArray->m_p = pNew_p; pArray->m_capacity = new_capacity; + return MZ_TRUE; +} + +static MZ_FORCEINLINE mz_bool mz_zip_array_reserve(mz_zip_archive *pZip, mz_zip_array *pArray, size_t new_capacity, mz_uint growing) +{ + if (new_capacity > pArray->m_capacity) { if (!mz_zip_array_ensure_capacity(pZip, pArray, new_capacity, growing)) return MZ_FALSE; } + return MZ_TRUE; +} + +static MZ_FORCEINLINE mz_bool mz_zip_array_resize(mz_zip_archive *pZip, mz_zip_array *pArray, size_t new_size, mz_uint growing) +{ + if (new_size > pArray->m_capacity) { if (!mz_zip_array_ensure_capacity(pZip, pArray, new_size, growing)) return MZ_FALSE; } + pArray->m_size = new_size; + return MZ_TRUE; +} + +static MZ_FORCEINLINE mz_bool mz_zip_array_ensure_room(mz_zip_archive *pZip, mz_zip_array *pArray, size_t n) +{ + return mz_zip_array_reserve(pZip, pArray, pArray->m_size + n, MZ_TRUE); +} + +static MZ_FORCEINLINE mz_bool mz_zip_array_push_back(mz_zip_archive *pZip, mz_zip_array *pArray, const void *pElements, size_t n) +{ + size_t orig_size = pArray->m_size; if (!mz_zip_array_resize(pZip, pArray, orig_size + n, MZ_TRUE)) return MZ_FALSE; + memcpy((mz_uint8*)pArray->m_p + orig_size * pArray->m_element_size, pElements, n * pArray->m_element_size); + return MZ_TRUE; +} + +#ifndef MINIZ_NO_TIME +static time_t mz_zip_dos_to_time_t(int dos_time, int dos_date) +{ + struct tm tm; + memset(&tm, 0, sizeof(tm)); tm.tm_isdst = -1; + tm.tm_year = ((dos_date >> 9) & 127) + 1980 - 1900; tm.tm_mon = ((dos_date >> 5) & 15) - 1; tm.tm_mday = dos_date & 31; + tm.tm_hour = (dos_time >> 11) & 31; tm.tm_min = (dos_time >> 5) & 63; tm.tm_sec = (dos_time << 1) & 62; + return mktime(&tm); +} + +static void mz_zip_time_to_dos_time(time_t time, mz_uint16 *pDOS_time, mz_uint16 *pDOS_date) +{ +#ifdef _MSC_VER + struct tm tm_struct; + struct tm *tm = &tm_struct; + errno_t err = localtime_s(tm, &time); + if (err) + { + *pDOS_date = 0; *pDOS_time = 0; + return; + } +#else + struct tm *tm = localtime(&time); +#endif + *pDOS_time = (mz_uint16)(((tm->tm_hour) << 11) + ((tm->tm_min) << 5) + ((tm->tm_sec) >> 1)); + *pDOS_date = (mz_uint16)(((tm->tm_year + 1900 - 1980) << 9) + ((tm->tm_mon + 1) << 5) + tm->tm_mday); +} +#endif + +#ifndef MINIZ_NO_STDIO +static mz_bool mz_zip_get_file_modified_time(const char *pFilename, mz_uint16 *pDOS_time, mz_uint16 *pDOS_date) +{ +#ifdef MINIZ_NO_TIME + (void)pFilename; *pDOS_date = *pDOS_time = 0; +#else + struct MZ_FILE_STAT_STRUCT file_stat; + // On Linux with x86 glibc, this call will fail on large files (>= 0x80000000 bytes) unless you compiled with _LARGEFILE64_SOURCE. Argh. + if (MZ_FILE_STAT(pFilename, &file_stat) != 0) + return MZ_FALSE; + mz_zip_time_to_dos_time(file_stat.st_mtime, pDOS_time, pDOS_date); +#endif // #ifdef MINIZ_NO_TIME + return MZ_TRUE; +} + +#ifndef MINIZ_NO_TIME +static mz_bool mz_zip_set_file_times(const char *pFilename, time_t access_time, time_t modified_time) +{ + struct utimbuf t; t.actime = access_time; t.modtime = modified_time; + return !utime(pFilename, &t); +} +#endif // #ifndef MINIZ_NO_TIME +#endif // #ifndef MINIZ_NO_STDIO + +static mz_bool mz_zip_reader_init_internal(mz_zip_archive *pZip, mz_uint32 flags) +{ + (void)flags; + if ((!pZip) || (pZip->m_pState) || (pZip->m_zip_mode != MZ_ZIP_MODE_INVALID)) + return MZ_FALSE; + + if (!pZip->m_pAlloc) pZip->m_pAlloc = def_alloc_func; + if (!pZip->m_pFree) pZip->m_pFree = def_free_func; + if (!pZip->m_pRealloc) pZip->m_pRealloc = def_realloc_func; + + pZip->m_zip_mode = MZ_ZIP_MODE_READING; + pZip->m_archive_size = 0; + pZip->m_central_directory_file_ofs = 0; + pZip->m_total_files = 0; + + if (NULL == (pZip->m_pState = (mz_zip_internal_state *)pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, sizeof(mz_zip_internal_state)))) + return MZ_FALSE; + memset(pZip->m_pState, 0, sizeof(mz_zip_internal_state)); + MZ_ZIP_ARRAY_SET_ELEMENT_SIZE(&pZip->m_pState->m_central_dir, sizeof(mz_uint8)); + MZ_ZIP_ARRAY_SET_ELEMENT_SIZE(&pZip->m_pState->m_central_dir_offsets, sizeof(mz_uint32)); + MZ_ZIP_ARRAY_SET_ELEMENT_SIZE(&pZip->m_pState->m_sorted_central_dir_offsets, sizeof(mz_uint32)); + return MZ_TRUE; +} + +static MZ_FORCEINLINE mz_bool mz_zip_reader_filename_less(const mz_zip_array *pCentral_dir_array, const mz_zip_array *pCentral_dir_offsets, mz_uint l_index, mz_uint r_index) +{ + const mz_uint8 *pL = &MZ_ZIP_ARRAY_ELEMENT(pCentral_dir_array, mz_uint8, MZ_ZIP_ARRAY_ELEMENT(pCentral_dir_offsets, mz_uint32, l_index)), *pE; + const mz_uint8 *pR = &MZ_ZIP_ARRAY_ELEMENT(pCentral_dir_array, mz_uint8, MZ_ZIP_ARRAY_ELEMENT(pCentral_dir_offsets, mz_uint32, r_index)); + mz_uint l_len = MZ_READ_LE16(pL + MZ_ZIP_CDH_FILENAME_LEN_OFS), r_len = MZ_READ_LE16(pR + MZ_ZIP_CDH_FILENAME_LEN_OFS); + mz_uint8 l = 0, r = 0; + pL += MZ_ZIP_CENTRAL_DIR_HEADER_SIZE; pR += MZ_ZIP_CENTRAL_DIR_HEADER_SIZE; + pE = pL + MZ_MIN(l_len, r_len); + while (pL < pE) + { + if ((l = MZ_TOLOWER(*pL)) != (r = MZ_TOLOWER(*pR))) + break; + pL++; pR++; + } + return (pL == pE) ? (l_len < r_len) : (l < r); +} + +#define MZ_SWAP_UINT32(a, b) do { mz_uint32 t = a; a = b; b = t; } MZ_MACRO_END + +// Heap sort of lowercased filenames, used to help accelerate plain central directory searches by mz_zip_reader_locate_file(). (Could also use qsort(), but it could allocate memory.) +static void mz_zip_reader_sort_central_dir_offsets_by_filename(mz_zip_archive *pZip) +{ + mz_zip_internal_state *pState = pZip->m_pState; + const mz_zip_array *pCentral_dir_offsets = &pState->m_central_dir_offsets; + const mz_zip_array *pCentral_dir = &pState->m_central_dir; + mz_uint32 *pIndices = &MZ_ZIP_ARRAY_ELEMENT(&pState->m_sorted_central_dir_offsets, mz_uint32, 0); + const int size = pZip->m_total_files; + int start = (size - 2) >> 1, end; + while (start >= 0) + { + int child, root = start; + for ( ; ; ) + { + if ((child = (root << 1) + 1) >= size) + break; + child += (((child + 1) < size) && (mz_zip_reader_filename_less(pCentral_dir, pCentral_dir_offsets, pIndices[child], pIndices[child + 1]))); + if (!mz_zip_reader_filename_less(pCentral_dir, pCentral_dir_offsets, pIndices[root], pIndices[child])) + break; + MZ_SWAP_UINT32(pIndices[root], pIndices[child]); root = child; + } + start--; + } + + end = size - 1; + while (end > 0) + { + int child, root = 0; + MZ_SWAP_UINT32(pIndices[end], pIndices[0]); + for ( ; ; ) + { + if ((child = (root << 1) + 1) >= end) + break; + child += (((child + 1) < end) && mz_zip_reader_filename_less(pCentral_dir, pCentral_dir_offsets, pIndices[child], pIndices[child + 1])); + if (!mz_zip_reader_filename_less(pCentral_dir, pCentral_dir_offsets, pIndices[root], pIndices[child])) + break; + MZ_SWAP_UINT32(pIndices[root], pIndices[child]); root = child; + } + end--; + } +} + +static mz_bool mz_zip_reader_read_central_dir(mz_zip_archive *pZip, mz_uint32 flags) +{ + mz_uint cdir_size, num_this_disk, cdir_disk_index; + mz_uint64 cdir_ofs; + mz_int64 cur_file_ofs; + const mz_uint8 *p; + mz_uint32 buf_u32[4096 / sizeof(mz_uint32)]; mz_uint8 *pBuf = (mz_uint8 *)buf_u32; + mz_bool sort_central_dir = ((flags & MZ_ZIP_FLAG_DO_NOT_SORT_CENTRAL_DIRECTORY) == 0); + // Basic sanity checks - reject files which are too small, and check the first 4 bytes of the file to make sure a local header is there. + if (pZip->m_archive_size < MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE) + return MZ_FALSE; + // Find the end of central directory record by scanning the file from the end towards the beginning. + cur_file_ofs = MZ_MAX((mz_int64)pZip->m_archive_size - (mz_int64)sizeof(buf_u32), 0); + for ( ; ; ) + { + int i, n = (int)MZ_MIN(sizeof(buf_u32), pZip->m_archive_size - cur_file_ofs); + if (pZip->m_pRead(pZip->m_pIO_opaque, cur_file_ofs, pBuf, n) != (mz_uint)n) + return MZ_FALSE; + for (i = n - 4; i >= 0; --i) + if (MZ_READ_LE32(pBuf + i) == MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIG) + break; + if (i >= 0) + { + cur_file_ofs += i; + break; + } + if ((!cur_file_ofs) || ((pZip->m_archive_size - cur_file_ofs) >= (0xFFFF + MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE))) + return MZ_FALSE; + cur_file_ofs = MZ_MAX(cur_file_ofs - (sizeof(buf_u32) - 3), 0); + } + // Read and verify the end of central directory record. + if (pZip->m_pRead(pZip->m_pIO_opaque, cur_file_ofs, pBuf, MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE) != MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE) + return MZ_FALSE; + if ((MZ_READ_LE32(pBuf + MZ_ZIP_ECDH_SIG_OFS) != MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIG) || + ((pZip->m_total_files = MZ_READ_LE16(pBuf + MZ_ZIP_ECDH_CDIR_TOTAL_ENTRIES_OFS)) != MZ_READ_LE16(pBuf + MZ_ZIP_ECDH_CDIR_NUM_ENTRIES_ON_DISK_OFS))) + return MZ_FALSE; + + num_this_disk = MZ_READ_LE16(pBuf + MZ_ZIP_ECDH_NUM_THIS_DISK_OFS); + cdir_disk_index = MZ_READ_LE16(pBuf + MZ_ZIP_ECDH_NUM_DISK_CDIR_OFS); + if (((num_this_disk | cdir_disk_index) != 0) && ((num_this_disk != 1) || (cdir_disk_index != 1))) + return MZ_FALSE; + + if ((cdir_size = MZ_READ_LE32(pBuf + MZ_ZIP_ECDH_CDIR_SIZE_OFS)) < pZip->m_total_files * MZ_ZIP_CENTRAL_DIR_HEADER_SIZE) + return MZ_FALSE; + + cdir_ofs = MZ_READ_LE32(pBuf + MZ_ZIP_ECDH_CDIR_OFS_OFS); + if ((cdir_ofs + (mz_uint64)cdir_size) > pZip->m_archive_size) + return MZ_FALSE; + + pZip->m_central_directory_file_ofs = cdir_ofs; + + if (pZip->m_total_files) + { + mz_uint i, n; + + // Read the entire central directory into a heap block, and allocate another heap block to hold the unsorted central dir file record offsets, and another to hold the sorted indices. + if ((!mz_zip_array_resize(pZip, &pZip->m_pState->m_central_dir, cdir_size, MZ_FALSE)) || + (!mz_zip_array_resize(pZip, &pZip->m_pState->m_central_dir_offsets, pZip->m_total_files, MZ_FALSE))) + return MZ_FALSE; + + if (sort_central_dir) + { + if (!mz_zip_array_resize(pZip, &pZip->m_pState->m_sorted_central_dir_offsets, pZip->m_total_files, MZ_FALSE)) + return MZ_FALSE; + } + + if (pZip->m_pRead(pZip->m_pIO_opaque, cdir_ofs, pZip->m_pState->m_central_dir.m_p, cdir_size) != cdir_size) + return MZ_FALSE; + + // Now create an index into the central directory file records, do some basic sanity checking on each record, and check for zip64 entries (which are not yet supported). + p = (const mz_uint8 *)pZip->m_pState->m_central_dir.m_p; + for (n = cdir_size, i = 0; i < pZip->m_total_files; ++i) + { + mz_uint total_header_size, comp_size, decomp_size, disk_index; + if ((n < MZ_ZIP_CENTRAL_DIR_HEADER_SIZE) || (MZ_READ_LE32(p) != MZ_ZIP_CENTRAL_DIR_HEADER_SIG)) + return MZ_FALSE; + MZ_ZIP_ARRAY_ELEMENT(&pZip->m_pState->m_central_dir_offsets, mz_uint32, i) = (mz_uint32)(p - (const mz_uint8 *)pZip->m_pState->m_central_dir.m_p); + if (sort_central_dir) + MZ_ZIP_ARRAY_ELEMENT(&pZip->m_pState->m_sorted_central_dir_offsets, mz_uint32, i) = i; + comp_size = MZ_READ_LE32(p + MZ_ZIP_CDH_COMPRESSED_SIZE_OFS); + decomp_size = MZ_READ_LE32(p + MZ_ZIP_CDH_DECOMPRESSED_SIZE_OFS); + if (((!MZ_READ_LE32(p + MZ_ZIP_CDH_METHOD_OFS)) && (decomp_size != comp_size)) || (decomp_size && !comp_size) || (decomp_size == 0xFFFFFFFF) || (comp_size == 0xFFFFFFFF)) + return MZ_FALSE; + disk_index = MZ_READ_LE16(p + MZ_ZIP_CDH_DISK_START_OFS); + if ((disk_index != num_this_disk) && (disk_index != 1)) + return MZ_FALSE; + if (((mz_uint64)MZ_READ_LE32(p + MZ_ZIP_CDH_LOCAL_HEADER_OFS) + MZ_ZIP_LOCAL_DIR_HEADER_SIZE + comp_size) > pZip->m_archive_size) + return MZ_FALSE; + if ((total_header_size = MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + MZ_READ_LE16(p + MZ_ZIP_CDH_FILENAME_LEN_OFS) + MZ_READ_LE16(p + MZ_ZIP_CDH_EXTRA_LEN_OFS) + MZ_READ_LE16(p + MZ_ZIP_CDH_COMMENT_LEN_OFS)) > n) + return MZ_FALSE; + n -= total_header_size; p += total_header_size; + } + } + + if (sort_central_dir) + mz_zip_reader_sort_central_dir_offsets_by_filename(pZip); + + return MZ_TRUE; +} + +mz_bool mz_zip_reader_init(mz_zip_archive *pZip, mz_uint64 size, mz_uint32 flags) +{ + if ((!pZip) || (!pZip->m_pRead)) + return MZ_FALSE; + if (!mz_zip_reader_init_internal(pZip, flags)) + return MZ_FALSE; + pZip->m_archive_size = size; + if (!mz_zip_reader_read_central_dir(pZip, flags)) + { + mz_zip_reader_end(pZip); + return MZ_FALSE; + } + return MZ_TRUE; +} + +static size_t mz_zip_mem_read_func(void *pOpaque, mz_uint64 file_ofs, void *pBuf, size_t n) +{ + mz_zip_archive *pZip = (mz_zip_archive *)pOpaque; + size_t s = (file_ofs >= pZip->m_archive_size) ? 0 : (size_t)MZ_MIN(pZip->m_archive_size - file_ofs, n); + memcpy(pBuf, (const mz_uint8 *)pZip->m_pState->m_pMem + file_ofs, s); + return s; +} + +mz_bool mz_zip_reader_init_mem(mz_zip_archive *pZip, const void *pMem, size_t size, mz_uint32 flags) +{ + if (!mz_zip_reader_init_internal(pZip, flags)) + return MZ_FALSE; + pZip->m_archive_size = size; + pZip->m_pRead = mz_zip_mem_read_func; + pZip->m_pIO_opaque = pZip; +#ifdef __cplusplus + pZip->m_pState->m_pMem = const_cast(pMem); +#else + pZip->m_pState->m_pMem = (void *)pMem; +#endif + pZip->m_pState->m_mem_size = size; + if (!mz_zip_reader_read_central_dir(pZip, flags)) + { + mz_zip_reader_end(pZip); + return MZ_FALSE; + } + return MZ_TRUE; +} + +#ifndef MINIZ_NO_STDIO +static size_t mz_zip_file_read_func(void *pOpaque, mz_uint64 file_ofs, void *pBuf, size_t n) +{ + mz_zip_archive *pZip = (mz_zip_archive *)pOpaque; + mz_int64 cur_ofs = MZ_FTELL64(pZip->m_pState->m_pFile); + if (((mz_int64)file_ofs < 0) || (((cur_ofs != (mz_int64)file_ofs)) && (MZ_FSEEK64(pZip->m_pState->m_pFile, (mz_int64)file_ofs, SEEK_SET)))) + return 0; + return MZ_FREAD(pBuf, 1, n, pZip->m_pState->m_pFile); +} + +mz_bool mz_zip_reader_init_file(mz_zip_archive *pZip, const char *pFilename, mz_uint32 flags) +{ + mz_uint64 file_size; + MZ_FILE *pFile = MZ_FOPEN(pFilename, "rb"); + if (!pFile) + return MZ_FALSE; + if (MZ_FSEEK64(pFile, 0, SEEK_END)) + { + MZ_FCLOSE(pFile); + return MZ_FALSE; + } + file_size = MZ_FTELL64(pFile); + if (!mz_zip_reader_init_internal(pZip, flags)) + { + MZ_FCLOSE(pFile); + return MZ_FALSE; + } + pZip->m_pRead = mz_zip_file_read_func; + pZip->m_pIO_opaque = pZip; + pZip->m_pState->m_pFile = pFile; + pZip->m_archive_size = file_size; + if (!mz_zip_reader_read_central_dir(pZip, flags)) + { + mz_zip_reader_end(pZip); + return MZ_FALSE; + } + return MZ_TRUE; +} +#endif // #ifndef MINIZ_NO_STDIO + +mz_uint mz_zip_reader_get_num_files(mz_zip_archive *pZip) +{ + return pZip ? pZip->m_total_files : 0; +} + +static MZ_FORCEINLINE const mz_uint8 *mz_zip_reader_get_cdh(mz_zip_archive *pZip, mz_uint file_index) +{ + if ((!pZip) || (!pZip->m_pState) || (file_index >= pZip->m_total_files) || (pZip->m_zip_mode != MZ_ZIP_MODE_READING)) + return NULL; + return &MZ_ZIP_ARRAY_ELEMENT(&pZip->m_pState->m_central_dir, mz_uint8, MZ_ZIP_ARRAY_ELEMENT(&pZip->m_pState->m_central_dir_offsets, mz_uint32, file_index)); +} + +mz_bool mz_zip_reader_is_file_encrypted(mz_zip_archive *pZip, mz_uint file_index) +{ + mz_uint m_bit_flag; + const mz_uint8 *p = mz_zip_reader_get_cdh(pZip, file_index); + if (!p) + return MZ_FALSE; + m_bit_flag = MZ_READ_LE16(p + MZ_ZIP_CDH_BIT_FLAG_OFS); + return (m_bit_flag & 1); +} + +mz_bool mz_zip_reader_is_file_a_directory(mz_zip_archive *pZip, mz_uint file_index) +{ + mz_uint filename_len, external_attr; + const mz_uint8 *p = mz_zip_reader_get_cdh(pZip, file_index); + if (!p) + return MZ_FALSE; + + // First see if the filename ends with a '/' character. + filename_len = MZ_READ_LE16(p + MZ_ZIP_CDH_FILENAME_LEN_OFS); + if (filename_len) + { + if (*(p + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + filename_len - 1) == '/') + return MZ_TRUE; + } + + // Bugfix: This code was also checking if the internal attribute was non-zero, which wasn't correct. + // Most/all zip writers (hopefully) set DOS file/directory attributes in the low 16-bits, so check for the DOS directory flag and ignore the source OS ID in the created by field. + // FIXME: Remove this check? Is it necessary - we already check the filename. + external_attr = MZ_READ_LE32(p + MZ_ZIP_CDH_EXTERNAL_ATTR_OFS); + if ((external_attr & 0x10) != 0) + return MZ_TRUE; + + return MZ_FALSE; +} + +mz_bool mz_zip_reader_file_stat(mz_zip_archive *pZip, mz_uint file_index, mz_zip_archive_file_stat *pStat) +{ + mz_uint n; + const mz_uint8 *p = mz_zip_reader_get_cdh(pZip, file_index); + if ((!p) || (!pStat)) + return MZ_FALSE; + + // Unpack the central directory record. + pStat->m_file_index = file_index; + pStat->m_central_dir_ofs = MZ_ZIP_ARRAY_ELEMENT(&pZip->m_pState->m_central_dir_offsets, mz_uint32, file_index); + pStat->m_version_made_by = MZ_READ_LE16(p + MZ_ZIP_CDH_VERSION_MADE_BY_OFS); + pStat->m_version_needed = MZ_READ_LE16(p + MZ_ZIP_CDH_VERSION_NEEDED_OFS); + pStat->m_bit_flag = MZ_READ_LE16(p + MZ_ZIP_CDH_BIT_FLAG_OFS); + pStat->m_method = MZ_READ_LE16(p + MZ_ZIP_CDH_METHOD_OFS); +#ifndef MINIZ_NO_TIME + pStat->m_time = mz_zip_dos_to_time_t(MZ_READ_LE16(p + MZ_ZIP_CDH_FILE_TIME_OFS), MZ_READ_LE16(p + MZ_ZIP_CDH_FILE_DATE_OFS)); +#endif + pStat->m_crc32 = MZ_READ_LE32(p + MZ_ZIP_CDH_CRC32_OFS); + pStat->m_comp_size = MZ_READ_LE32(p + MZ_ZIP_CDH_COMPRESSED_SIZE_OFS); + pStat->m_uncomp_size = MZ_READ_LE32(p + MZ_ZIP_CDH_DECOMPRESSED_SIZE_OFS); + pStat->m_internal_attr = MZ_READ_LE16(p + MZ_ZIP_CDH_INTERNAL_ATTR_OFS); + pStat->m_external_attr = MZ_READ_LE32(p + MZ_ZIP_CDH_EXTERNAL_ATTR_OFS); + pStat->m_local_header_ofs = MZ_READ_LE32(p + MZ_ZIP_CDH_LOCAL_HEADER_OFS); + + // Copy as much of the filename and comment as possible. + n = MZ_READ_LE16(p + MZ_ZIP_CDH_FILENAME_LEN_OFS); n = MZ_MIN(n, MZ_ZIP_MAX_ARCHIVE_FILENAME_SIZE - 1); + memcpy(pStat->m_filename, p + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE, n); pStat->m_filename[n] = '\0'; + + n = MZ_READ_LE16(p + MZ_ZIP_CDH_COMMENT_LEN_OFS); n = MZ_MIN(n, MZ_ZIP_MAX_ARCHIVE_FILE_COMMENT_SIZE - 1); + pStat->m_comment_size = n; + memcpy(pStat->m_comment, p + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + MZ_READ_LE16(p + MZ_ZIP_CDH_FILENAME_LEN_OFS) + MZ_READ_LE16(p + MZ_ZIP_CDH_EXTRA_LEN_OFS), n); pStat->m_comment[n] = '\0'; + + return MZ_TRUE; +} + +mz_uint mz_zip_reader_get_filename(mz_zip_archive *pZip, mz_uint file_index, char *pFilename, mz_uint filename_buf_size) +{ + mz_uint n; + const mz_uint8 *p = mz_zip_reader_get_cdh(pZip, file_index); + if (!p) { if (filename_buf_size) pFilename[0] = '\0'; return 0; } + n = MZ_READ_LE16(p + MZ_ZIP_CDH_FILENAME_LEN_OFS); + if (filename_buf_size) + { + n = MZ_MIN(n, filename_buf_size - 1); + memcpy(pFilename, p + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE, n); + pFilename[n] = '\0'; + } + return n + 1; +} + +static MZ_FORCEINLINE mz_bool mz_zip_reader_string_equal(const char *pA, const char *pB, mz_uint len, mz_uint flags) +{ + mz_uint i; + if (flags & MZ_ZIP_FLAG_CASE_SENSITIVE) + return 0 == memcmp(pA, pB, len); + for (i = 0; i < len; ++i) + if (MZ_TOLOWER(pA[i]) != MZ_TOLOWER(pB[i])) + return MZ_FALSE; + return MZ_TRUE; +} + +static MZ_FORCEINLINE int mz_zip_reader_filename_compare(const mz_zip_array *pCentral_dir_array, const mz_zip_array *pCentral_dir_offsets, mz_uint l_index, const char *pR, mz_uint r_len) +{ + const mz_uint8 *pL = &MZ_ZIP_ARRAY_ELEMENT(pCentral_dir_array, mz_uint8, MZ_ZIP_ARRAY_ELEMENT(pCentral_dir_offsets, mz_uint32, l_index)), *pE; + mz_uint l_len = MZ_READ_LE16(pL + MZ_ZIP_CDH_FILENAME_LEN_OFS); + mz_uint8 l = 0, r = 0; + pL += MZ_ZIP_CENTRAL_DIR_HEADER_SIZE; + pE = pL + MZ_MIN(l_len, r_len); + while (pL < pE) + { + if ((l = MZ_TOLOWER(*pL)) != (r = MZ_TOLOWER(*pR))) + break; + pL++; pR++; + } + return (pL == pE) ? (int)(l_len - r_len) : (l - r); +} + +static int mz_zip_reader_locate_file_binary_search(mz_zip_archive *pZip, const char *pFilename) +{ + mz_zip_internal_state *pState = pZip->m_pState; + const mz_zip_array *pCentral_dir_offsets = &pState->m_central_dir_offsets; + const mz_zip_array *pCentral_dir = &pState->m_central_dir; + mz_uint32 *pIndices = &MZ_ZIP_ARRAY_ELEMENT(&pState->m_sorted_central_dir_offsets, mz_uint32, 0); + const int size = pZip->m_total_files; + const mz_uint filename_len = (mz_uint)strlen(pFilename); + int l = 0, h = size - 1; + while (l <= h) + { + int m = (l + h) >> 1, file_index = pIndices[m], comp = mz_zip_reader_filename_compare(pCentral_dir, pCentral_dir_offsets, file_index, pFilename, filename_len); + if (!comp) + return file_index; + else if (comp < 0) + l = m + 1; + else + h = m - 1; + } + return -1; +} + +int mz_zip_reader_locate_file(mz_zip_archive *pZip, const char *pName, const char *pComment, mz_uint flags) +{ + mz_uint file_index; size_t name_len, comment_len; + if ((!pZip) || (!pZip->m_pState) || (!pName) || (pZip->m_zip_mode != MZ_ZIP_MODE_READING)) + return -1; + if (((flags & (MZ_ZIP_FLAG_IGNORE_PATH | MZ_ZIP_FLAG_CASE_SENSITIVE)) == 0) && (!pComment) && (pZip->m_pState->m_sorted_central_dir_offsets.m_size)) + return mz_zip_reader_locate_file_binary_search(pZip, pName); + name_len = strlen(pName); if (name_len > 0xFFFF) return -1; + comment_len = pComment ? strlen(pComment) : 0; if (comment_len > 0xFFFF) return -1; + for (file_index = 0; file_index < pZip->m_total_files; file_index++) + { + const mz_uint8 *pHeader = &MZ_ZIP_ARRAY_ELEMENT(&pZip->m_pState->m_central_dir, mz_uint8, MZ_ZIP_ARRAY_ELEMENT(&pZip->m_pState->m_central_dir_offsets, mz_uint32, file_index)); + mz_uint filename_len = MZ_READ_LE16(pHeader + MZ_ZIP_CDH_FILENAME_LEN_OFS); + const char *pFilename = (const char *)pHeader + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE; + if (filename_len < name_len) + continue; + if (comment_len) + { + mz_uint file_extra_len = MZ_READ_LE16(pHeader + MZ_ZIP_CDH_EXTRA_LEN_OFS), file_comment_len = MZ_READ_LE16(pHeader + MZ_ZIP_CDH_COMMENT_LEN_OFS); + const char *pFile_comment = pFilename + filename_len + file_extra_len; + if ((file_comment_len != comment_len) || (!mz_zip_reader_string_equal(pComment, pFile_comment, file_comment_len, flags))) + continue; + } + if ((flags & MZ_ZIP_FLAG_IGNORE_PATH) && (filename_len)) + { + int ofs = filename_len - 1; + do + { + if ((pFilename[ofs] == '/') || (pFilename[ofs] == '\\') || (pFilename[ofs] == ':')) + break; + } while (--ofs >= 0); + ofs++; + pFilename += ofs; filename_len -= ofs; + } + if ((filename_len == name_len) && (mz_zip_reader_string_equal(pName, pFilename, filename_len, flags))) + return file_index; + } + return -1; +} + +mz_bool mz_zip_reader_extract_to_mem_no_alloc(mz_zip_archive *pZip, mz_uint file_index, void *pBuf, size_t buf_size, mz_uint flags, void *pUser_read_buf, size_t user_read_buf_size) +{ + int status = TINFL_STATUS_DONE; + mz_uint64 needed_size, cur_file_ofs, comp_remaining, out_buf_ofs = 0, read_buf_size, read_buf_ofs = 0, read_buf_avail; + mz_zip_archive_file_stat file_stat; + void *pRead_buf; + mz_uint32 local_header_u32[(MZ_ZIP_LOCAL_DIR_HEADER_SIZE + sizeof(mz_uint32) - 1) / sizeof(mz_uint32)]; mz_uint8 *pLocal_header = (mz_uint8 *)local_header_u32; + tinfl_decompressor inflator; + + if ((buf_size) && (!pBuf)) + return MZ_FALSE; + + if (!mz_zip_reader_file_stat(pZip, file_index, &file_stat)) + return MZ_FALSE; + + // Empty file, or a directory (but not always a directory - I've seen odd zips with directories that have compressed data which inflates to 0 bytes) + if (!file_stat.m_comp_size) + return MZ_TRUE; + + // Entry is a subdirectory (I've seen old zips with dir entries which have compressed deflate data which inflates to 0 bytes, but these entries claim to uncompress to 512 bytes in the headers). + // I'm torn how to handle this case - should it fail instead? + if (mz_zip_reader_is_file_a_directory(pZip, file_index)) + return MZ_TRUE; + + // Encryption and patch files are not supported. + if (file_stat.m_bit_flag & (1 | 32)) + return MZ_FALSE; + + // This function only supports stored and deflate. + if ((!(flags & MZ_ZIP_FLAG_COMPRESSED_DATA)) && (file_stat.m_method != 0) && (file_stat.m_method != MZ_DEFLATED)) + return MZ_FALSE; + + // Ensure supplied output buffer is large enough. + needed_size = (flags & MZ_ZIP_FLAG_COMPRESSED_DATA) ? file_stat.m_comp_size : file_stat.m_uncomp_size; + if (buf_size < needed_size) + return MZ_FALSE; + + // Read and parse the local directory entry. + cur_file_ofs = file_stat.m_local_header_ofs; + if (pZip->m_pRead(pZip->m_pIO_opaque, cur_file_ofs, pLocal_header, MZ_ZIP_LOCAL_DIR_HEADER_SIZE) != MZ_ZIP_LOCAL_DIR_HEADER_SIZE) + return MZ_FALSE; + if (MZ_READ_LE32(pLocal_header) != MZ_ZIP_LOCAL_DIR_HEADER_SIG) + return MZ_FALSE; + + cur_file_ofs += MZ_ZIP_LOCAL_DIR_HEADER_SIZE + MZ_READ_LE16(pLocal_header + MZ_ZIP_LDH_FILENAME_LEN_OFS) + MZ_READ_LE16(pLocal_header + MZ_ZIP_LDH_EXTRA_LEN_OFS); + if ((cur_file_ofs + file_stat.m_comp_size) > pZip->m_archive_size) + return MZ_FALSE; + + if ((flags & MZ_ZIP_FLAG_COMPRESSED_DATA) || (!file_stat.m_method)) + { + // The file is stored or the caller has requested the compressed data. + if (pZip->m_pRead(pZip->m_pIO_opaque, cur_file_ofs, pBuf, (size_t)needed_size) != needed_size) + return MZ_FALSE; + return ((flags & MZ_ZIP_FLAG_COMPRESSED_DATA) != 0) || (mz_crc32(MZ_CRC32_INIT, (const mz_uint8 *)pBuf, (size_t)file_stat.m_uncomp_size) == file_stat.m_crc32); + } + + // Decompress the file either directly from memory or from a file input buffer. + tinfl_init(&inflator); + + if (pZip->m_pState->m_pMem) + { + // Read directly from the archive in memory. + pRead_buf = (mz_uint8 *)pZip->m_pState->m_pMem + cur_file_ofs; + read_buf_size = read_buf_avail = file_stat.m_comp_size; + comp_remaining = 0; + } + else if (pUser_read_buf) + { + // Use a user provided read buffer. + if (!user_read_buf_size) + return MZ_FALSE; + pRead_buf = (mz_uint8 *)pUser_read_buf; + read_buf_size = user_read_buf_size; + read_buf_avail = 0; + comp_remaining = file_stat.m_comp_size; + } + else + { + // Temporarily allocate a read buffer. + read_buf_size = MZ_MIN(file_stat.m_comp_size, MZ_ZIP_MAX_IO_BUF_SIZE); +#ifdef _MSC_VER + if (((0, sizeof(size_t) == sizeof(mz_uint32))) && (read_buf_size > 0x7FFFFFFF)) +#else + if (((sizeof(size_t) == sizeof(mz_uint32))) && (read_buf_size > 0x7FFFFFFF)) +#endif + return MZ_FALSE; + if (NULL == (pRead_buf = pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, (size_t)read_buf_size))) + return MZ_FALSE; + read_buf_avail = 0; + comp_remaining = file_stat.m_comp_size; + } + + do + { + size_t in_buf_size, out_buf_size = (size_t)(file_stat.m_uncomp_size - out_buf_ofs); + if ((!read_buf_avail) && (!pZip->m_pState->m_pMem)) + { + read_buf_avail = MZ_MIN(read_buf_size, comp_remaining); + if (pZip->m_pRead(pZip->m_pIO_opaque, cur_file_ofs, pRead_buf, (size_t)read_buf_avail) != read_buf_avail) + { + status = TINFL_STATUS_FAILED; + break; + } + cur_file_ofs += read_buf_avail; + comp_remaining -= read_buf_avail; + read_buf_ofs = 0; + } + in_buf_size = (size_t)read_buf_avail; + status = tinfl_decompress(&inflator, (mz_uint8 *)pRead_buf + read_buf_ofs, &in_buf_size, (mz_uint8 *)pBuf, (mz_uint8 *)pBuf + out_buf_ofs, &out_buf_size, TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF | (comp_remaining ? TINFL_FLAG_HAS_MORE_INPUT : 0)); + read_buf_avail -= in_buf_size; + read_buf_ofs += in_buf_size; + out_buf_ofs += out_buf_size; + } while (status == TINFL_STATUS_NEEDS_MORE_INPUT); + + if (status == TINFL_STATUS_DONE) + { + // Make sure the entire file was decompressed, and check its CRC. + if ((out_buf_ofs != file_stat.m_uncomp_size) || (mz_crc32(MZ_CRC32_INIT, (const mz_uint8 *)pBuf, (size_t)file_stat.m_uncomp_size) != file_stat.m_crc32)) + status = TINFL_STATUS_FAILED; + } + + if ((!pZip->m_pState->m_pMem) && (!pUser_read_buf)) + pZip->m_pFree(pZip->m_pAlloc_opaque, pRead_buf); + + return status == TINFL_STATUS_DONE; +} + +mz_bool mz_zip_reader_extract_file_to_mem_no_alloc(mz_zip_archive *pZip, const char *pFilename, void *pBuf, size_t buf_size, mz_uint flags, void *pUser_read_buf, size_t user_read_buf_size) +{ + int file_index = mz_zip_reader_locate_file(pZip, pFilename, NULL, flags); + if (file_index < 0) + return MZ_FALSE; + return mz_zip_reader_extract_to_mem_no_alloc(pZip, file_index, pBuf, buf_size, flags, pUser_read_buf, user_read_buf_size); +} + +mz_bool mz_zip_reader_extract_to_mem(mz_zip_archive *pZip, mz_uint file_index, void *pBuf, size_t buf_size, mz_uint flags) +{ + return mz_zip_reader_extract_to_mem_no_alloc(pZip, file_index, pBuf, buf_size, flags, NULL, 0); +} + +mz_bool mz_zip_reader_extract_file_to_mem(mz_zip_archive *pZip, const char *pFilename, void *pBuf, size_t buf_size, mz_uint flags) +{ + return mz_zip_reader_extract_file_to_mem_no_alloc(pZip, pFilename, pBuf, buf_size, flags, NULL, 0); +} + +void *mz_zip_reader_extract_to_heap(mz_zip_archive *pZip, mz_uint file_index, size_t *pSize, mz_uint flags) +{ + mz_uint64 comp_size, uncomp_size, alloc_size; + const mz_uint8 *p = mz_zip_reader_get_cdh(pZip, file_index); + void *pBuf; + + if (pSize) + *pSize = 0; + if (!p) + return NULL; + + comp_size = MZ_READ_LE32(p + MZ_ZIP_CDH_COMPRESSED_SIZE_OFS); + uncomp_size = MZ_READ_LE32(p + MZ_ZIP_CDH_DECOMPRESSED_SIZE_OFS); + + alloc_size = (flags & MZ_ZIP_FLAG_COMPRESSED_DATA) ? comp_size : uncomp_size; +#ifdef _MSC_VER + if (((0, sizeof(size_t) == sizeof(mz_uint32))) && (alloc_size > 0x7FFFFFFF)) +#else + if (((sizeof(size_t) == sizeof(mz_uint32))) && (alloc_size > 0x7FFFFFFF)) +#endif + return NULL; + if (NULL == (pBuf = pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, (size_t)alloc_size))) + return NULL; + + if (!mz_zip_reader_extract_to_mem(pZip, file_index, pBuf, (size_t)alloc_size, flags)) + { + pZip->m_pFree(pZip->m_pAlloc_opaque, pBuf); + return NULL; + } + + if (pSize) *pSize = (size_t)alloc_size; + return pBuf; +} + +void *mz_zip_reader_extract_file_to_heap(mz_zip_archive *pZip, const char *pFilename, size_t *pSize, mz_uint flags) +{ + int file_index = mz_zip_reader_locate_file(pZip, pFilename, NULL, flags); + if (file_index < 0) + { + if (pSize) *pSize = 0; + return MZ_FALSE; + } + return mz_zip_reader_extract_to_heap(pZip, file_index, pSize, flags); +} + +mz_bool mz_zip_reader_extract_to_callback(mz_zip_archive *pZip, mz_uint file_index, mz_file_write_func pCallback, void *pOpaque, mz_uint flags) +{ + int status = TINFL_STATUS_DONE; mz_uint file_crc32 = MZ_CRC32_INIT; + mz_uint64 read_buf_size, read_buf_ofs = 0, read_buf_avail, comp_remaining, out_buf_ofs = 0, cur_file_ofs; + mz_zip_archive_file_stat file_stat; + void *pRead_buf = NULL; void *pWrite_buf = NULL; + mz_uint32 local_header_u32[(MZ_ZIP_LOCAL_DIR_HEADER_SIZE + sizeof(mz_uint32) - 1) / sizeof(mz_uint32)]; mz_uint8 *pLocal_header = (mz_uint8 *)local_header_u32; + + if (!mz_zip_reader_file_stat(pZip, file_index, &file_stat)) + return MZ_FALSE; + + // Empty file, or a directory (but not always a directory - I've seen odd zips with directories that have compressed data which inflates to 0 bytes) + if (!file_stat.m_comp_size) + return MZ_TRUE; + + // Entry is a subdirectory (I've seen old zips with dir entries which have compressed deflate data which inflates to 0 bytes, but these entries claim to uncompress to 512 bytes in the headers). + // I'm torn how to handle this case - should it fail instead? + if (mz_zip_reader_is_file_a_directory(pZip, file_index)) + return MZ_TRUE; + + // Encryption and patch files are not supported. + if (file_stat.m_bit_flag & (1 | 32)) + return MZ_FALSE; + + // This function only supports stored and deflate. + if ((!(flags & MZ_ZIP_FLAG_COMPRESSED_DATA)) && (file_stat.m_method != 0) && (file_stat.m_method != MZ_DEFLATED)) + return MZ_FALSE; + + // Read and parse the local directory entry. + cur_file_ofs = file_stat.m_local_header_ofs; + if (pZip->m_pRead(pZip->m_pIO_opaque, cur_file_ofs, pLocal_header, MZ_ZIP_LOCAL_DIR_HEADER_SIZE) != MZ_ZIP_LOCAL_DIR_HEADER_SIZE) + return MZ_FALSE; + if (MZ_READ_LE32(pLocal_header) != MZ_ZIP_LOCAL_DIR_HEADER_SIG) + return MZ_FALSE; + + cur_file_ofs += MZ_ZIP_LOCAL_DIR_HEADER_SIZE + MZ_READ_LE16(pLocal_header + MZ_ZIP_LDH_FILENAME_LEN_OFS) + MZ_READ_LE16(pLocal_header + MZ_ZIP_LDH_EXTRA_LEN_OFS); + if ((cur_file_ofs + file_stat.m_comp_size) > pZip->m_archive_size) + return MZ_FALSE; + + // Decompress the file either directly from memory or from a file input buffer. + if (pZip->m_pState->m_pMem) + { + pRead_buf = (mz_uint8 *)pZip->m_pState->m_pMem + cur_file_ofs; + read_buf_size = read_buf_avail = file_stat.m_comp_size; + comp_remaining = 0; + } + else + { + read_buf_size = MZ_MIN(file_stat.m_comp_size, MZ_ZIP_MAX_IO_BUF_SIZE); + if (NULL == (pRead_buf = pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, (size_t)read_buf_size))) + return MZ_FALSE; + read_buf_avail = 0; + comp_remaining = file_stat.m_comp_size; + } + + if ((flags & MZ_ZIP_FLAG_COMPRESSED_DATA) || (!file_stat.m_method)) + { + // The file is stored or the caller has requested the compressed data. + if (pZip->m_pState->m_pMem) + { +#ifdef _MSC_VER + if (((0, sizeof(size_t) == sizeof(mz_uint32))) && (file_stat.m_comp_size > 0xFFFFFFFF)) +#else + if (((sizeof(size_t) == sizeof(mz_uint32))) && (file_stat.m_comp_size > 0xFFFFFFFF)) +#endif + return MZ_FALSE; + if (pCallback(pOpaque, out_buf_ofs, pRead_buf, (size_t)file_stat.m_comp_size) != file_stat.m_comp_size) + status = TINFL_STATUS_FAILED; + else if (!(flags & MZ_ZIP_FLAG_COMPRESSED_DATA)) + file_crc32 = (mz_uint32)mz_crc32(file_crc32, (const mz_uint8 *)pRead_buf, (size_t)file_stat.m_comp_size); + cur_file_ofs += file_stat.m_comp_size; + out_buf_ofs += file_stat.m_comp_size; + comp_remaining = 0; + } + else + { + while (comp_remaining) + { + read_buf_avail = MZ_MIN(read_buf_size, comp_remaining); + if (pZip->m_pRead(pZip->m_pIO_opaque, cur_file_ofs, pRead_buf, (size_t)read_buf_avail) != read_buf_avail) + { + status = TINFL_STATUS_FAILED; + break; + } + + if (!(flags & MZ_ZIP_FLAG_COMPRESSED_DATA)) + file_crc32 = (mz_uint32)mz_crc32(file_crc32, (const mz_uint8 *)pRead_buf, (size_t)read_buf_avail); + + if (pCallback(pOpaque, out_buf_ofs, pRead_buf, (size_t)read_buf_avail) != read_buf_avail) + { + status = TINFL_STATUS_FAILED; + break; + } + cur_file_ofs += read_buf_avail; + out_buf_ofs += read_buf_avail; + comp_remaining -= read_buf_avail; + } + } + } + else + { + tinfl_decompressor inflator; + tinfl_init(&inflator); + + if (NULL == (pWrite_buf = pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, TINFL_LZ_DICT_SIZE))) + status = TINFL_STATUS_FAILED; + else + { + do + { + mz_uint8 *pWrite_buf_cur = (mz_uint8 *)pWrite_buf + (out_buf_ofs & (TINFL_LZ_DICT_SIZE - 1)); + size_t in_buf_size, out_buf_size = TINFL_LZ_DICT_SIZE - (out_buf_ofs & (TINFL_LZ_DICT_SIZE - 1)); + if ((!read_buf_avail) && (!pZip->m_pState->m_pMem)) + { + read_buf_avail = MZ_MIN(read_buf_size, comp_remaining); + if (pZip->m_pRead(pZip->m_pIO_opaque, cur_file_ofs, pRead_buf, (size_t)read_buf_avail) != read_buf_avail) + { + status = TINFL_STATUS_FAILED; + break; + } + cur_file_ofs += read_buf_avail; + comp_remaining -= read_buf_avail; + read_buf_ofs = 0; + } + + in_buf_size = (size_t)read_buf_avail; + status = tinfl_decompress(&inflator, (const mz_uint8 *)pRead_buf + read_buf_ofs, &in_buf_size, (mz_uint8 *)pWrite_buf, pWrite_buf_cur, &out_buf_size, comp_remaining ? TINFL_FLAG_HAS_MORE_INPUT : 0); + read_buf_avail -= in_buf_size; + read_buf_ofs += in_buf_size; + + if (out_buf_size) + { + if (pCallback(pOpaque, out_buf_ofs, pWrite_buf_cur, out_buf_size) != out_buf_size) + { + status = TINFL_STATUS_FAILED; + break; + } + file_crc32 = (mz_uint32)mz_crc32(file_crc32, pWrite_buf_cur, out_buf_size); + if ((out_buf_ofs += out_buf_size) > file_stat.m_uncomp_size) + { + status = TINFL_STATUS_FAILED; + break; + } + } + } while ((status == TINFL_STATUS_NEEDS_MORE_INPUT) || (status == TINFL_STATUS_HAS_MORE_OUTPUT)); + } + } + + if ((status == TINFL_STATUS_DONE) && (!(flags & MZ_ZIP_FLAG_COMPRESSED_DATA))) + { + // Make sure the entire file was decompressed, and check its CRC. + if ((out_buf_ofs != file_stat.m_uncomp_size) || (file_crc32 != file_stat.m_crc32)) + status = TINFL_STATUS_FAILED; + } + + if (!pZip->m_pState->m_pMem) + pZip->m_pFree(pZip->m_pAlloc_opaque, pRead_buf); + if (pWrite_buf) + pZip->m_pFree(pZip->m_pAlloc_opaque, pWrite_buf); + + return status == TINFL_STATUS_DONE; +} + +mz_bool mz_zip_reader_extract_file_to_callback(mz_zip_archive *pZip, const char *pFilename, mz_file_write_func pCallback, void *pOpaque, mz_uint flags) +{ + int file_index = mz_zip_reader_locate_file(pZip, pFilename, NULL, flags); + if (file_index < 0) + return MZ_FALSE; + return mz_zip_reader_extract_to_callback(pZip, file_index, pCallback, pOpaque, flags); +} + +#ifndef MINIZ_NO_STDIO +static size_t mz_zip_file_write_callback(void *pOpaque, mz_uint64 ofs, const void *pBuf, size_t n) +{ + (void)ofs; return MZ_FWRITE(pBuf, 1, n, (MZ_FILE*)pOpaque); +} + +mz_bool mz_zip_reader_extract_to_file(mz_zip_archive *pZip, mz_uint file_index, const char *pDst_filename, mz_uint flags) +{ + mz_bool status; + mz_zip_archive_file_stat file_stat; + MZ_FILE *pFile; + if (!mz_zip_reader_file_stat(pZip, file_index, &file_stat)) + return MZ_FALSE; + pFile = MZ_FOPEN(pDst_filename, "wb"); + if (!pFile) + return MZ_FALSE; + status = mz_zip_reader_extract_to_callback(pZip, file_index, mz_zip_file_write_callback, pFile, flags); + if (MZ_FCLOSE(pFile) == EOF) + return MZ_FALSE; +#ifndef MINIZ_NO_TIME + if (status) + mz_zip_set_file_times(pDst_filename, file_stat.m_time, file_stat.m_time); +#endif + return status; +} +#endif // #ifndef MINIZ_NO_STDIO + +mz_bool mz_zip_reader_end(mz_zip_archive *pZip) +{ + if ((!pZip) || (!pZip->m_pState) || (!pZip->m_pAlloc) || (!pZip->m_pFree) || (pZip->m_zip_mode != MZ_ZIP_MODE_READING)) + return MZ_FALSE; + + if (pZip->m_pState) + { + mz_zip_internal_state *pState = pZip->m_pState; pZip->m_pState = NULL; + mz_zip_array_clear(pZip, &pState->m_central_dir); + mz_zip_array_clear(pZip, &pState->m_central_dir_offsets); + mz_zip_array_clear(pZip, &pState->m_sorted_central_dir_offsets); + +#ifndef MINIZ_NO_STDIO + if (pState->m_pFile) + { + MZ_FCLOSE(pState->m_pFile); + pState->m_pFile = NULL; + } +#endif // #ifndef MINIZ_NO_STDIO + + pZip->m_pFree(pZip->m_pAlloc_opaque, pState); + } + pZip->m_zip_mode = MZ_ZIP_MODE_INVALID; + + return MZ_TRUE; +} + +#ifndef MINIZ_NO_STDIO +mz_bool mz_zip_reader_extract_file_to_file(mz_zip_archive *pZip, const char *pArchive_filename, const char *pDst_filename, mz_uint flags) +{ + int file_index = mz_zip_reader_locate_file(pZip, pArchive_filename, NULL, flags); + if (file_index < 0) + return MZ_FALSE; + return mz_zip_reader_extract_to_file(pZip, file_index, pDst_filename, flags); +} +#endif + +// ------------------- .ZIP archive writing + +#ifndef MINIZ_NO_ARCHIVE_WRITING_APIS + +static void mz_write_le16(mz_uint8 *p, mz_uint16 v) { p[0] = (mz_uint8)v; p[1] = (mz_uint8)(v >> 8); } +static void mz_write_le32(mz_uint8 *p, mz_uint32 v) { p[0] = (mz_uint8)v; p[1] = (mz_uint8)(v >> 8); p[2] = (mz_uint8)(v >> 16); p[3] = (mz_uint8)(v >> 24); } +#define MZ_WRITE_LE16(p, v) mz_write_le16((mz_uint8 *)(p), (mz_uint16)(v)) +#define MZ_WRITE_LE32(p, v) mz_write_le32((mz_uint8 *)(p), (mz_uint32)(v)) + +mz_bool mz_zip_writer_init(mz_zip_archive *pZip, mz_uint64 existing_size) +{ + if ((!pZip) || (pZip->m_pState) || (!pZip->m_pWrite) || (pZip->m_zip_mode != MZ_ZIP_MODE_INVALID)) + return MZ_FALSE; + + if (pZip->m_file_offset_alignment) + { + // Ensure user specified file offset alignment is a power of 2. + if (pZip->m_file_offset_alignment & (pZip->m_file_offset_alignment - 1)) + return MZ_FALSE; + } + + if (!pZip->m_pAlloc) pZip->m_pAlloc = def_alloc_func; + if (!pZip->m_pFree) pZip->m_pFree = def_free_func; + if (!pZip->m_pRealloc) pZip->m_pRealloc = def_realloc_func; + + pZip->m_zip_mode = MZ_ZIP_MODE_WRITING; + pZip->m_archive_size = existing_size; + pZip->m_central_directory_file_ofs = 0; + pZip->m_total_files = 0; + + if (NULL == (pZip->m_pState = (mz_zip_internal_state *)pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, sizeof(mz_zip_internal_state)))) + return MZ_FALSE; + memset(pZip->m_pState, 0, sizeof(mz_zip_internal_state)); + MZ_ZIP_ARRAY_SET_ELEMENT_SIZE(&pZip->m_pState->m_central_dir, sizeof(mz_uint8)); + MZ_ZIP_ARRAY_SET_ELEMENT_SIZE(&pZip->m_pState->m_central_dir_offsets, sizeof(mz_uint32)); + MZ_ZIP_ARRAY_SET_ELEMENT_SIZE(&pZip->m_pState->m_sorted_central_dir_offsets, sizeof(mz_uint32)); + return MZ_TRUE; +} + +static size_t mz_zip_heap_write_func(void *pOpaque, mz_uint64 file_ofs, const void *pBuf, size_t n) +{ + mz_zip_archive *pZip = (mz_zip_archive *)pOpaque; + mz_zip_internal_state *pState = pZip->m_pState; + mz_uint64 new_size = MZ_MAX(file_ofs + n, pState->m_mem_size); +#ifdef _MSC_VER + if ((!n) || ((0, sizeof(size_t) == sizeof(mz_uint32)) && (new_size > 0x7FFFFFFF))) +#else + if ((!n) || ((sizeof(size_t) == sizeof(mz_uint32)) && (new_size > 0x7FFFFFFF))) +#endif + return 0; + if (new_size > pState->m_mem_capacity) + { + void *pNew_block; + size_t new_capacity = MZ_MAX(64, pState->m_mem_capacity); while (new_capacity < new_size) new_capacity *= 2; + if (NULL == (pNew_block = pZip->m_pRealloc(pZip->m_pAlloc_opaque, pState->m_pMem, 1, new_capacity))) + return 0; + pState->m_pMem = pNew_block; pState->m_mem_capacity = new_capacity; + } + memcpy((mz_uint8 *)pState->m_pMem + file_ofs, pBuf, n); + pState->m_mem_size = (size_t)new_size; + return n; +} + +mz_bool mz_zip_writer_init_heap(mz_zip_archive *pZip, size_t size_to_reserve_at_beginning, size_t initial_allocation_size) +{ + pZip->m_pWrite = mz_zip_heap_write_func; + pZip->m_pIO_opaque = pZip; + if (!mz_zip_writer_init(pZip, size_to_reserve_at_beginning)) + return MZ_FALSE; + if (0 != (initial_allocation_size = MZ_MAX(initial_allocation_size, size_to_reserve_at_beginning))) + { + if (NULL == (pZip->m_pState->m_pMem = pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, initial_allocation_size))) + { + mz_zip_writer_end(pZip); + return MZ_FALSE; + } + pZip->m_pState->m_mem_capacity = initial_allocation_size; + } + return MZ_TRUE; +} + +#ifndef MINIZ_NO_STDIO +static size_t mz_zip_file_write_func(void *pOpaque, mz_uint64 file_ofs, const void *pBuf, size_t n) +{ + mz_zip_archive *pZip = (mz_zip_archive *)pOpaque; + mz_int64 cur_ofs = MZ_FTELL64(pZip->m_pState->m_pFile); + if (((mz_int64)file_ofs < 0) || (((cur_ofs != (mz_int64)file_ofs)) && (MZ_FSEEK64(pZip->m_pState->m_pFile, (mz_int64)file_ofs, SEEK_SET)))) + return 0; + return MZ_FWRITE(pBuf, 1, n, pZip->m_pState->m_pFile); +} + +mz_bool mz_zip_writer_init_file(mz_zip_archive *pZip, const char *pFilename, mz_uint64 size_to_reserve_at_beginning) +{ + MZ_FILE *pFile; + pZip->m_pWrite = mz_zip_file_write_func; + pZip->m_pIO_opaque = pZip; + if (!mz_zip_writer_init(pZip, size_to_reserve_at_beginning)) + return MZ_FALSE; + if (NULL == (pFile = MZ_FOPEN(pFilename, "wb"))) + { + mz_zip_writer_end(pZip); + return MZ_FALSE; + } + pZip->m_pState->m_pFile = pFile; + if (size_to_reserve_at_beginning) + { + mz_uint64 cur_ofs = 0; char buf[4096]; MZ_CLEAR_OBJ(buf); + do + { + size_t n = (size_t)MZ_MIN(sizeof(buf), size_to_reserve_at_beginning); + if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_ofs, buf, n) != n) + { + mz_zip_writer_end(pZip); + return MZ_FALSE; + } + cur_ofs += n; size_to_reserve_at_beginning -= n; + } while (size_to_reserve_at_beginning); + } + return MZ_TRUE; +} +#endif // #ifndef MINIZ_NO_STDIO + +mz_bool mz_zip_writer_init_from_reader(mz_zip_archive *pZip, const char *pFilename) +{ + mz_zip_internal_state *pState; + if ((!pZip) || (!pZip->m_pState) || (pZip->m_zip_mode != MZ_ZIP_MODE_READING)) + return MZ_FALSE; + // No sense in trying to write to an archive that's already at the support max size + if ((pZip->m_total_files == 0xFFFF) || ((pZip->m_archive_size + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + MZ_ZIP_LOCAL_DIR_HEADER_SIZE) > 0xFFFFFFFF)) + return MZ_FALSE; + + pState = pZip->m_pState; + + if (pState->m_pFile) + { +#ifdef MINIZ_NO_STDIO + pFilename; return MZ_FALSE; +#else + // Archive is being read from stdio - try to reopen as writable. + if (pZip->m_pIO_opaque != pZip) + return MZ_FALSE; + if (!pFilename) + return MZ_FALSE; + pZip->m_pWrite = mz_zip_file_write_func; + if (NULL == (pState->m_pFile = MZ_FREOPEN(pFilename, "r+b", pState->m_pFile))) + { + // The mz_zip_archive is now in a bogus state because pState->m_pFile is NULL, so just close it. + mz_zip_reader_end(pZip); + return MZ_FALSE; + } +#endif // #ifdef MINIZ_NO_STDIO + } + else if (pState->m_pMem) + { + // Archive lives in a memory block. Assume it's from the heap that we can resize using the realloc callback. + if (pZip->m_pIO_opaque != pZip) + return MZ_FALSE; + pState->m_mem_capacity = pState->m_mem_size; + pZip->m_pWrite = mz_zip_heap_write_func; + } + // Archive is being read via a user provided read function - make sure the user has specified a write function too. + else if (!pZip->m_pWrite) + return MZ_FALSE; + + // Start writing new files at the archive's current central directory location. + pZip->m_archive_size = pZip->m_central_directory_file_ofs; + pZip->m_zip_mode = MZ_ZIP_MODE_WRITING; + pZip->m_central_directory_file_ofs = 0; + + return MZ_TRUE; +} + +mz_bool mz_zip_writer_add_mem(mz_zip_archive *pZip, const char *pArchive_name, const void *pBuf, size_t buf_size, mz_uint level_and_flags) +{ + return mz_zip_writer_add_mem_ex(pZip, pArchive_name, pBuf, buf_size, NULL, 0, level_and_flags, 0, 0); +} + +typedef struct +{ + mz_zip_archive *m_pZip; + mz_uint64 m_cur_archive_file_ofs; + mz_uint64 m_comp_size; +} mz_zip_writer_add_state; + +static mz_bool mz_zip_writer_add_put_buf_callback(const void* pBuf, int len, void *pUser) +{ + mz_zip_writer_add_state *pState = (mz_zip_writer_add_state *)pUser; + if ((int)pState->m_pZip->m_pWrite(pState->m_pZip->m_pIO_opaque, pState->m_cur_archive_file_ofs, pBuf, len) != len) + return MZ_FALSE; + pState->m_cur_archive_file_ofs += len; + pState->m_comp_size += len; + return MZ_TRUE; +} + +static mz_bool mz_zip_writer_create_local_dir_header(mz_zip_archive *pZip, mz_uint8 *pDst, mz_uint16 filename_size, mz_uint16 extra_size, mz_uint64 uncomp_size, mz_uint64 comp_size, mz_uint32 uncomp_crc32, mz_uint16 method, mz_uint16 bit_flags, mz_uint16 dos_time, mz_uint16 dos_date) +{ + (void)pZip; + memset(pDst, 0, MZ_ZIP_LOCAL_DIR_HEADER_SIZE); + MZ_WRITE_LE32(pDst + MZ_ZIP_LDH_SIG_OFS, MZ_ZIP_LOCAL_DIR_HEADER_SIG); + MZ_WRITE_LE16(pDst + MZ_ZIP_LDH_VERSION_NEEDED_OFS, method ? 20 : 0); + MZ_WRITE_LE16(pDst + MZ_ZIP_LDH_BIT_FLAG_OFS, bit_flags); + MZ_WRITE_LE16(pDst + MZ_ZIP_LDH_METHOD_OFS, method); + MZ_WRITE_LE16(pDst + MZ_ZIP_LDH_FILE_TIME_OFS, dos_time); + MZ_WRITE_LE16(pDst + MZ_ZIP_LDH_FILE_DATE_OFS, dos_date); + MZ_WRITE_LE32(pDst + MZ_ZIP_LDH_CRC32_OFS, uncomp_crc32); + MZ_WRITE_LE32(pDst + MZ_ZIP_LDH_COMPRESSED_SIZE_OFS, comp_size); + MZ_WRITE_LE32(pDst + MZ_ZIP_LDH_DECOMPRESSED_SIZE_OFS, uncomp_size); + MZ_WRITE_LE16(pDst + MZ_ZIP_LDH_FILENAME_LEN_OFS, filename_size); + MZ_WRITE_LE16(pDst + MZ_ZIP_LDH_EXTRA_LEN_OFS, extra_size); + return MZ_TRUE; +} + +static mz_bool mz_zip_writer_create_central_dir_header(mz_zip_archive *pZip, mz_uint8 *pDst, mz_uint16 filename_size, mz_uint16 extra_size, mz_uint16 comment_size, mz_uint64 uncomp_size, mz_uint64 comp_size, mz_uint32 uncomp_crc32, mz_uint16 method, mz_uint16 bit_flags, mz_uint16 dos_time, mz_uint16 dos_date, mz_uint64 local_header_ofs, mz_uint32 ext_attributes) +{ + (void)pZip; + memset(pDst, 0, MZ_ZIP_CENTRAL_DIR_HEADER_SIZE); + MZ_WRITE_LE32(pDst + MZ_ZIP_CDH_SIG_OFS, MZ_ZIP_CENTRAL_DIR_HEADER_SIG); + MZ_WRITE_LE16(pDst + MZ_ZIP_CDH_VERSION_NEEDED_OFS, method ? 20 : 0); + MZ_WRITE_LE16(pDst + MZ_ZIP_CDH_BIT_FLAG_OFS, bit_flags); + MZ_WRITE_LE16(pDst + MZ_ZIP_CDH_METHOD_OFS, method); + MZ_WRITE_LE16(pDst + MZ_ZIP_CDH_FILE_TIME_OFS, dos_time); + MZ_WRITE_LE16(pDst + MZ_ZIP_CDH_FILE_DATE_OFS, dos_date); + MZ_WRITE_LE32(pDst + MZ_ZIP_CDH_CRC32_OFS, uncomp_crc32); + MZ_WRITE_LE32(pDst + MZ_ZIP_CDH_COMPRESSED_SIZE_OFS, comp_size); + MZ_WRITE_LE32(pDst + MZ_ZIP_CDH_DECOMPRESSED_SIZE_OFS, uncomp_size); + MZ_WRITE_LE16(pDst + MZ_ZIP_CDH_FILENAME_LEN_OFS, filename_size); + MZ_WRITE_LE16(pDst + MZ_ZIP_CDH_EXTRA_LEN_OFS, extra_size); + MZ_WRITE_LE16(pDst + MZ_ZIP_CDH_COMMENT_LEN_OFS, comment_size); + MZ_WRITE_LE32(pDst + MZ_ZIP_CDH_EXTERNAL_ATTR_OFS, ext_attributes); + MZ_WRITE_LE32(pDst + MZ_ZIP_CDH_LOCAL_HEADER_OFS, local_header_ofs); + return MZ_TRUE; +} + +static mz_bool mz_zip_writer_add_to_central_dir(mz_zip_archive *pZip, const char *pFilename, mz_uint16 filename_size, const void *pExtra, mz_uint16 extra_size, const void *pComment, mz_uint16 comment_size, mz_uint64 uncomp_size, mz_uint64 comp_size, mz_uint32 uncomp_crc32, mz_uint16 method, mz_uint16 bit_flags, mz_uint16 dos_time, mz_uint16 dos_date, mz_uint64 local_header_ofs, mz_uint32 ext_attributes) +{ + mz_zip_internal_state *pState = pZip->m_pState; + mz_uint32 central_dir_ofs = (mz_uint32)pState->m_central_dir.m_size; + size_t orig_central_dir_size = pState->m_central_dir.m_size; + mz_uint8 central_dir_header[MZ_ZIP_CENTRAL_DIR_HEADER_SIZE]; + + // No zip64 support yet + if ((local_header_ofs > 0xFFFFFFFF) || (((mz_uint64)pState->m_central_dir.m_size + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + filename_size + extra_size + comment_size) > 0xFFFFFFFF)) + return MZ_FALSE; + + if (!mz_zip_writer_create_central_dir_header(pZip, central_dir_header, filename_size, extra_size, comment_size, uncomp_size, comp_size, uncomp_crc32, method, bit_flags, dos_time, dos_date, local_header_ofs, ext_attributes)) + return MZ_FALSE; + + if ((!mz_zip_array_push_back(pZip, &pState->m_central_dir, central_dir_header, MZ_ZIP_CENTRAL_DIR_HEADER_SIZE)) || + (!mz_zip_array_push_back(pZip, &pState->m_central_dir, pFilename, filename_size)) || + (!mz_zip_array_push_back(pZip, &pState->m_central_dir, pExtra, extra_size)) || + (!mz_zip_array_push_back(pZip, &pState->m_central_dir, pComment, comment_size)) || + (!mz_zip_array_push_back(pZip, &pState->m_central_dir_offsets, ¢ral_dir_ofs, 1))) + { + // Try to push the central directory array back into its original state. + mz_zip_array_resize(pZip, &pState->m_central_dir, orig_central_dir_size, MZ_FALSE); + return MZ_FALSE; + } + + return MZ_TRUE; +} + +static mz_bool mz_zip_writer_validate_archive_name(const char *pArchive_name) +{ + // Basic ZIP archive filename validity checks: Valid filenames cannot start with a forward slash, cannot contain a drive letter, and cannot use DOS-style backward slashes. + if (*pArchive_name == '/') + return MZ_FALSE; + while (*pArchive_name) + { + if ((*pArchive_name == '\\') || (*pArchive_name == ':')) + return MZ_FALSE; + pArchive_name++; + } + return MZ_TRUE; +} + +static mz_uint mz_zip_writer_compute_padding_needed_for_file_alignment(mz_zip_archive *pZip) +{ + mz_uint32 n; + if (!pZip->m_file_offset_alignment) + return 0; + n = (mz_uint32)(pZip->m_archive_size & (pZip->m_file_offset_alignment - 1)); + return (pZip->m_file_offset_alignment - n) & (pZip->m_file_offset_alignment - 1); +} + +static mz_bool mz_zip_writer_write_zeros(mz_zip_archive *pZip, mz_uint64 cur_file_ofs, mz_uint32 n) +{ + char buf[4096]; + memset(buf, 0, MZ_MIN(sizeof(buf), n)); + while (n) + { + mz_uint32 s = MZ_MIN(sizeof(buf), n); + if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_file_ofs, buf, s) != s) + return MZ_FALSE; + cur_file_ofs += s; n -= s; + } + return MZ_TRUE; +} + +mz_bool mz_zip_writer_add_mem_ex(mz_zip_archive *pZip, const char *pArchive_name, const void *pBuf, size_t buf_size, const void *pComment, mz_uint16 comment_size, mz_uint level_and_flags, mz_uint64 uncomp_size, mz_uint32 uncomp_crc32) +{ + mz_uint16 method = 0, dos_time = 0, dos_date = 0; + mz_uint level, ext_attributes = 0, num_alignment_padding_bytes; + mz_uint64 local_dir_header_ofs = pZip->m_archive_size, cur_archive_file_ofs = pZip->m_archive_size, comp_size = 0; + size_t archive_name_size; + mz_uint8 local_dir_header[MZ_ZIP_LOCAL_DIR_HEADER_SIZE]; + tdefl_compressor *pComp = NULL; + mz_bool store_data_uncompressed; + mz_zip_internal_state *pState; + + if ((int)level_and_flags < 0) + level_and_flags = MZ_DEFAULT_LEVEL; + level = level_and_flags & 0xF; + store_data_uncompressed = ((!level) || (level_and_flags & MZ_ZIP_FLAG_COMPRESSED_DATA)); + + if ((!pZip) || (!pZip->m_pState) || (pZip->m_zip_mode != MZ_ZIP_MODE_WRITING) || ((buf_size) && (!pBuf)) || (!pArchive_name) || ((comment_size) && (!pComment)) || (pZip->m_total_files == 0xFFFF) || (level > MZ_UBER_COMPRESSION)) + return MZ_FALSE; + + pState = pZip->m_pState; + + if ((!(level_and_flags & MZ_ZIP_FLAG_COMPRESSED_DATA)) && (uncomp_size)) + return MZ_FALSE; + // No zip64 support yet + if ((buf_size > 0xFFFFFFFF) || (uncomp_size > 0xFFFFFFFF)) + return MZ_FALSE; + if (!mz_zip_writer_validate_archive_name(pArchive_name)) + return MZ_FALSE; + +#ifndef MINIZ_NO_TIME + { + time_t cur_time; time(&cur_time); + mz_zip_time_to_dos_time(cur_time, &dos_time, &dos_date); + } +#endif // #ifndef MINIZ_NO_TIME + + archive_name_size = strlen(pArchive_name); + if (archive_name_size > 0xFFFF) + return MZ_FALSE; + + num_alignment_padding_bytes = mz_zip_writer_compute_padding_needed_for_file_alignment(pZip); + + // no zip64 support yet + if ((pZip->m_total_files == 0xFFFF) || ((pZip->m_archive_size + num_alignment_padding_bytes + MZ_ZIP_LOCAL_DIR_HEADER_SIZE + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + comment_size + archive_name_size) > 0xFFFFFFFF)) + return MZ_FALSE; + + if ((archive_name_size) && (pArchive_name[archive_name_size - 1] == '/')) + { + // Set DOS Subdirectory attribute bit. + ext_attributes |= 0x10; + // Subdirectories cannot contain data. + if ((buf_size) || (uncomp_size)) + return MZ_FALSE; + } + + // Try to do any allocations before writing to the archive, so if an allocation fails the file remains unmodified. (A good idea if we're doing an in-place modification.) + if ((!mz_zip_array_ensure_room(pZip, &pState->m_central_dir, MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + archive_name_size + comment_size)) || (!mz_zip_array_ensure_room(pZip, &pState->m_central_dir_offsets, 1))) + return MZ_FALSE; + + if ((!store_data_uncompressed) && (buf_size)) + { + if (NULL == (pComp = (tdefl_compressor *)pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, sizeof(tdefl_compressor)))) + return MZ_FALSE; + } + + if (!mz_zip_writer_write_zeros(pZip, cur_archive_file_ofs, num_alignment_padding_bytes + sizeof(local_dir_header))) + { + pZip->m_pFree(pZip->m_pAlloc_opaque, pComp); + return MZ_FALSE; + } + local_dir_header_ofs += num_alignment_padding_bytes; + if (pZip->m_file_offset_alignment) { MZ_ASSERT((local_dir_header_ofs & (pZip->m_file_offset_alignment - 1)) == 0); } + cur_archive_file_ofs += num_alignment_padding_bytes + sizeof(local_dir_header); + + MZ_CLEAR_OBJ(local_dir_header); + if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_file_ofs, pArchive_name, archive_name_size) != archive_name_size) + { + pZip->m_pFree(pZip->m_pAlloc_opaque, pComp); + return MZ_FALSE; + } + cur_archive_file_ofs += archive_name_size; + + if (!(level_and_flags & MZ_ZIP_FLAG_COMPRESSED_DATA)) + { + uncomp_crc32 = (mz_uint32)mz_crc32(MZ_CRC32_INIT, (const mz_uint8*)pBuf, buf_size); + uncomp_size = buf_size; + if (uncomp_size <= 3) + { + level = 0; + store_data_uncompressed = MZ_TRUE; + } + } + + if (store_data_uncompressed) + { + if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_file_ofs, pBuf, buf_size) != buf_size) + { + pZip->m_pFree(pZip->m_pAlloc_opaque, pComp); + return MZ_FALSE; + } + + cur_archive_file_ofs += buf_size; + comp_size = buf_size; + + if (level_and_flags & MZ_ZIP_FLAG_COMPRESSED_DATA) + method = MZ_DEFLATED; + } + else if (buf_size) + { + mz_zip_writer_add_state state; + + state.m_pZip = pZip; + state.m_cur_archive_file_ofs = cur_archive_file_ofs; + state.m_comp_size = 0; + + if ((tdefl_init(pComp, mz_zip_writer_add_put_buf_callback, &state, tdefl_create_comp_flags_from_zip_params(level, -15, MZ_DEFAULT_STRATEGY)) != TDEFL_STATUS_OKAY) || + (tdefl_compress_buffer(pComp, pBuf, buf_size, TDEFL_FINISH) != TDEFL_STATUS_DONE)) + { + pZip->m_pFree(pZip->m_pAlloc_opaque, pComp); + return MZ_FALSE; + } + + comp_size = state.m_comp_size; + cur_archive_file_ofs = state.m_cur_archive_file_ofs; + + method = MZ_DEFLATED; + } + + pZip->m_pFree(pZip->m_pAlloc_opaque, pComp); + pComp = NULL; + + // no zip64 support yet + if ((comp_size > 0xFFFFFFFF) || (cur_archive_file_ofs > 0xFFFFFFFF)) + return MZ_FALSE; + + if (!mz_zip_writer_create_local_dir_header(pZip, local_dir_header, (mz_uint16)archive_name_size, 0, uncomp_size, comp_size, uncomp_crc32, method, 0, dos_time, dos_date)) + return MZ_FALSE; + + if (pZip->m_pWrite(pZip->m_pIO_opaque, local_dir_header_ofs, local_dir_header, sizeof(local_dir_header)) != sizeof(local_dir_header)) + return MZ_FALSE; + + if (!mz_zip_writer_add_to_central_dir(pZip, pArchive_name, (mz_uint16)archive_name_size, NULL, 0, pComment, comment_size, uncomp_size, comp_size, uncomp_crc32, method, 0, dos_time, dos_date, local_dir_header_ofs, ext_attributes)) + return MZ_FALSE; + + pZip->m_total_files++; + pZip->m_archive_size = cur_archive_file_ofs; + + return MZ_TRUE; +} + +#ifndef MINIZ_NO_STDIO +mz_bool mz_zip_writer_add_file(mz_zip_archive *pZip, const char *pArchive_name, const char *pSrc_filename, const void *pComment, mz_uint16 comment_size, mz_uint level_and_flags) +{ + mz_uint uncomp_crc32 = MZ_CRC32_INIT, level, num_alignment_padding_bytes; + mz_uint16 method = 0, dos_time = 0, dos_date = 0, ext_attributes = 0; + mz_uint64 local_dir_header_ofs = pZip->m_archive_size, cur_archive_file_ofs = pZip->m_archive_size, uncomp_size = 0, comp_size = 0; + size_t archive_name_size; + mz_uint8 local_dir_header[MZ_ZIP_LOCAL_DIR_HEADER_SIZE]; + MZ_FILE *pSrc_file = NULL; + + if ((int)level_and_flags < 0) + level_and_flags = MZ_DEFAULT_LEVEL; + level = level_and_flags & 0xF; + + if ((!pZip) || (!pZip->m_pState) || (pZip->m_zip_mode != MZ_ZIP_MODE_WRITING) || (!pArchive_name) || ((comment_size) && (!pComment)) || (level > MZ_UBER_COMPRESSION)) + return MZ_FALSE; + if (level_and_flags & MZ_ZIP_FLAG_COMPRESSED_DATA) + return MZ_FALSE; + if (!mz_zip_writer_validate_archive_name(pArchive_name)) + return MZ_FALSE; + + archive_name_size = strlen(pArchive_name); + if (archive_name_size > 0xFFFF) + return MZ_FALSE; + + num_alignment_padding_bytes = mz_zip_writer_compute_padding_needed_for_file_alignment(pZip); + + // no zip64 support yet + if ((pZip->m_total_files == 0xFFFF) || ((pZip->m_archive_size + num_alignment_padding_bytes + MZ_ZIP_LOCAL_DIR_HEADER_SIZE + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + comment_size + archive_name_size) > 0xFFFFFFFF)) + return MZ_FALSE; + + if (!mz_zip_get_file_modified_time(pSrc_filename, &dos_time, &dos_date)) + return MZ_FALSE; + + pSrc_file = MZ_FOPEN(pSrc_filename, "rb"); + if (!pSrc_file) + return MZ_FALSE; + MZ_FSEEK64(pSrc_file, 0, SEEK_END); + uncomp_size = MZ_FTELL64(pSrc_file); + MZ_FSEEK64(pSrc_file, 0, SEEK_SET); + + if (uncomp_size > 0xFFFFFFFF) + { + // No zip64 support yet + MZ_FCLOSE(pSrc_file); + return MZ_FALSE; + } + if (uncomp_size <= 3) + level = 0; + + if (!mz_zip_writer_write_zeros(pZip, cur_archive_file_ofs, num_alignment_padding_bytes + sizeof(local_dir_header))) + { + MZ_FCLOSE(pSrc_file); + return MZ_FALSE; + } + local_dir_header_ofs += num_alignment_padding_bytes; + if (pZip->m_file_offset_alignment) { MZ_ASSERT((local_dir_header_ofs & (pZip->m_file_offset_alignment - 1)) == 0); } + cur_archive_file_ofs += num_alignment_padding_bytes + sizeof(local_dir_header); + + MZ_CLEAR_OBJ(local_dir_header); + if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_file_ofs, pArchive_name, archive_name_size) != archive_name_size) + { + MZ_FCLOSE(pSrc_file); + return MZ_FALSE; + } + cur_archive_file_ofs += archive_name_size; + + if (uncomp_size) + { + mz_uint64 uncomp_remaining = uncomp_size; + void *pRead_buf = pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, MZ_ZIP_MAX_IO_BUF_SIZE); + if (!pRead_buf) + { + MZ_FCLOSE(pSrc_file); + return MZ_FALSE; + } + + if (!level) + { + while (uncomp_remaining) + { + mz_uint n = (mz_uint)MZ_MIN(MZ_ZIP_MAX_IO_BUF_SIZE, uncomp_remaining); + if ((MZ_FREAD(pRead_buf, 1, n, pSrc_file) != n) || (pZip->m_pWrite(pZip->m_pIO_opaque, cur_archive_file_ofs, pRead_buf, n) != n)) + { + pZip->m_pFree(pZip->m_pAlloc_opaque, pRead_buf); + MZ_FCLOSE(pSrc_file); + return MZ_FALSE; + } + uncomp_crc32 = (mz_uint32)mz_crc32(uncomp_crc32, (const mz_uint8 *)pRead_buf, n); + uncomp_remaining -= n; + cur_archive_file_ofs += n; + } + comp_size = uncomp_size; + } + else + { + mz_bool result = MZ_FALSE; + mz_zip_writer_add_state state; + tdefl_compressor *pComp = (tdefl_compressor *)pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, sizeof(tdefl_compressor)); + if (!pComp) + { + pZip->m_pFree(pZip->m_pAlloc_opaque, pRead_buf); + MZ_FCLOSE(pSrc_file); + return MZ_FALSE; + } + + state.m_pZip = pZip; + state.m_cur_archive_file_ofs = cur_archive_file_ofs; + state.m_comp_size = 0; + + if (tdefl_init(pComp, mz_zip_writer_add_put_buf_callback, &state, tdefl_create_comp_flags_from_zip_params(level, -15, MZ_DEFAULT_STRATEGY)) != TDEFL_STATUS_OKAY) + { + pZip->m_pFree(pZip->m_pAlloc_opaque, pComp); + pZip->m_pFree(pZip->m_pAlloc_opaque, pRead_buf); + MZ_FCLOSE(pSrc_file); + return MZ_FALSE; + } + + for ( ; ; ) + { + size_t in_buf_size = (mz_uint32)MZ_MIN(uncomp_remaining, MZ_ZIP_MAX_IO_BUF_SIZE); + tdefl_status status; + + if (MZ_FREAD(pRead_buf, 1, in_buf_size, pSrc_file) != in_buf_size) + break; + + uncomp_crc32 = (mz_uint32)mz_crc32(uncomp_crc32, (const mz_uint8 *)pRead_buf, in_buf_size); + uncomp_remaining -= in_buf_size; + + status = tdefl_compress_buffer(pComp, pRead_buf, in_buf_size, uncomp_remaining ? TDEFL_NO_FLUSH : TDEFL_FINISH); + if (status == TDEFL_STATUS_DONE) + { + result = MZ_TRUE; + break; + } + else if (status != TDEFL_STATUS_OKAY) + break; + } + + pZip->m_pFree(pZip->m_pAlloc_opaque, pComp); + + if (!result) + { + pZip->m_pFree(pZip->m_pAlloc_opaque, pRead_buf); + MZ_FCLOSE(pSrc_file); + return MZ_FALSE; + } + + comp_size = state.m_comp_size; + cur_archive_file_ofs = state.m_cur_archive_file_ofs; + + method = MZ_DEFLATED; + } + + pZip->m_pFree(pZip->m_pAlloc_opaque, pRead_buf); + } + + MZ_FCLOSE(pSrc_file); pSrc_file = NULL; + + // no zip64 support yet + if ((comp_size > 0xFFFFFFFF) || (cur_archive_file_ofs > 0xFFFFFFFF)) + return MZ_FALSE; + + if (!mz_zip_writer_create_local_dir_header(pZip, local_dir_header, (mz_uint16)archive_name_size, 0, uncomp_size, comp_size, uncomp_crc32, method, 0, dos_time, dos_date)) + return MZ_FALSE; + + if (pZip->m_pWrite(pZip->m_pIO_opaque, local_dir_header_ofs, local_dir_header, sizeof(local_dir_header)) != sizeof(local_dir_header)) + return MZ_FALSE; + + if (!mz_zip_writer_add_to_central_dir(pZip, pArchive_name, (mz_uint16)archive_name_size, NULL, 0, pComment, comment_size, uncomp_size, comp_size, uncomp_crc32, method, 0, dos_time, dos_date, local_dir_header_ofs, ext_attributes)) + return MZ_FALSE; + + pZip->m_total_files++; + pZip->m_archive_size = cur_archive_file_ofs; + + return MZ_TRUE; +} +#endif // #ifndef MINIZ_NO_STDIO + +mz_bool mz_zip_writer_add_from_zip_reader(mz_zip_archive *pZip, mz_zip_archive *pSource_zip, mz_uint file_index) +{ + mz_uint n, bit_flags, num_alignment_padding_bytes; + mz_uint64 comp_bytes_remaining, local_dir_header_ofs; + mz_uint64 cur_src_file_ofs, cur_dst_file_ofs; + mz_uint32 local_header_u32[(MZ_ZIP_LOCAL_DIR_HEADER_SIZE + sizeof(mz_uint32) - 1) / sizeof(mz_uint32)]; mz_uint8 *pLocal_header = (mz_uint8 *)local_header_u32; + mz_uint8 central_header[MZ_ZIP_CENTRAL_DIR_HEADER_SIZE]; + size_t orig_central_dir_size; + mz_zip_internal_state *pState; + void *pBuf; const mz_uint8 *pSrc_central_header; + + if ((!pZip) || (!pZip->m_pState) || (pZip->m_zip_mode != MZ_ZIP_MODE_WRITING)) + return MZ_FALSE; + if (NULL == (pSrc_central_header = mz_zip_reader_get_cdh(pSource_zip, file_index))) + return MZ_FALSE; + pState = pZip->m_pState; + + num_alignment_padding_bytes = mz_zip_writer_compute_padding_needed_for_file_alignment(pZip); + + // no zip64 support yet + if ((pZip->m_total_files == 0xFFFF) || ((pZip->m_archive_size + num_alignment_padding_bytes + MZ_ZIP_LOCAL_DIR_HEADER_SIZE + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE) > 0xFFFFFFFF)) + return MZ_FALSE; + + cur_src_file_ofs = MZ_READ_LE32(pSrc_central_header + MZ_ZIP_CDH_LOCAL_HEADER_OFS); + cur_dst_file_ofs = pZip->m_archive_size; + + if (pSource_zip->m_pRead(pSource_zip->m_pIO_opaque, cur_src_file_ofs, pLocal_header, MZ_ZIP_LOCAL_DIR_HEADER_SIZE) != MZ_ZIP_LOCAL_DIR_HEADER_SIZE) + return MZ_FALSE; + if (MZ_READ_LE32(pLocal_header) != MZ_ZIP_LOCAL_DIR_HEADER_SIG) + return MZ_FALSE; + cur_src_file_ofs += MZ_ZIP_LOCAL_DIR_HEADER_SIZE; + + if (!mz_zip_writer_write_zeros(pZip, cur_dst_file_ofs, num_alignment_padding_bytes)) + return MZ_FALSE; + cur_dst_file_ofs += num_alignment_padding_bytes; + local_dir_header_ofs = cur_dst_file_ofs; + if (pZip->m_file_offset_alignment) { MZ_ASSERT((local_dir_header_ofs & (pZip->m_file_offset_alignment - 1)) == 0); } + + if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_dst_file_ofs, pLocal_header, MZ_ZIP_LOCAL_DIR_HEADER_SIZE) != MZ_ZIP_LOCAL_DIR_HEADER_SIZE) + return MZ_FALSE; + cur_dst_file_ofs += MZ_ZIP_LOCAL_DIR_HEADER_SIZE; + + n = MZ_READ_LE16(pLocal_header + MZ_ZIP_LDH_FILENAME_LEN_OFS) + MZ_READ_LE16(pLocal_header + MZ_ZIP_LDH_EXTRA_LEN_OFS); + comp_bytes_remaining = n + MZ_READ_LE32(pSrc_central_header + MZ_ZIP_CDH_COMPRESSED_SIZE_OFS); + + if (NULL == (pBuf = pZip->m_pAlloc(pZip->m_pAlloc_opaque, 1, (size_t)MZ_MAX(sizeof(mz_uint32) * 4, MZ_MIN(MZ_ZIP_MAX_IO_BUF_SIZE, comp_bytes_remaining))))) + return MZ_FALSE; + + while (comp_bytes_remaining) + { + n = (mz_uint)MZ_MIN(MZ_ZIP_MAX_IO_BUF_SIZE, comp_bytes_remaining); + if (pSource_zip->m_pRead(pSource_zip->m_pIO_opaque, cur_src_file_ofs, pBuf, n) != n) + { + pZip->m_pFree(pZip->m_pAlloc_opaque, pBuf); + return MZ_FALSE; + } + cur_src_file_ofs += n; + + if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_dst_file_ofs, pBuf, n) != n) + { + pZip->m_pFree(pZip->m_pAlloc_opaque, pBuf); + return MZ_FALSE; + } + cur_dst_file_ofs += n; + + comp_bytes_remaining -= n; + } + + bit_flags = MZ_READ_LE16(pLocal_header + MZ_ZIP_LDH_BIT_FLAG_OFS); + if (bit_flags & 8) + { + // Copy data descriptor + if (pSource_zip->m_pRead(pSource_zip->m_pIO_opaque, cur_src_file_ofs, pBuf, sizeof(mz_uint32) * 4) != sizeof(mz_uint32) * 4) + { + pZip->m_pFree(pZip->m_pAlloc_opaque, pBuf); + return MZ_FALSE; + } + + n = sizeof(mz_uint32) * ((MZ_READ_LE32(pBuf) == 0x08074b50) ? 4 : 3); + if (pZip->m_pWrite(pZip->m_pIO_opaque, cur_dst_file_ofs, pBuf, n) != n) + { + pZip->m_pFree(pZip->m_pAlloc_opaque, pBuf); + return MZ_FALSE; + } + + cur_src_file_ofs += n; + cur_dst_file_ofs += n; + } + pZip->m_pFree(pZip->m_pAlloc_opaque, pBuf); + + // no zip64 support yet + if (cur_dst_file_ofs > 0xFFFFFFFF) + return MZ_FALSE; + + orig_central_dir_size = pState->m_central_dir.m_size; + + memcpy(central_header, pSrc_central_header, MZ_ZIP_CENTRAL_DIR_HEADER_SIZE); + MZ_WRITE_LE32(central_header + MZ_ZIP_CDH_LOCAL_HEADER_OFS, local_dir_header_ofs); + if (!mz_zip_array_push_back(pZip, &pState->m_central_dir, central_header, MZ_ZIP_CENTRAL_DIR_HEADER_SIZE)) + return MZ_FALSE; + + n = MZ_READ_LE16(pSrc_central_header + MZ_ZIP_CDH_FILENAME_LEN_OFS) + MZ_READ_LE16(pSrc_central_header + MZ_ZIP_CDH_EXTRA_LEN_OFS) + MZ_READ_LE16(pSrc_central_header + MZ_ZIP_CDH_COMMENT_LEN_OFS); + if (!mz_zip_array_push_back(pZip, &pState->m_central_dir, pSrc_central_header + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE, n)) + { + mz_zip_array_resize(pZip, &pState->m_central_dir, orig_central_dir_size, MZ_FALSE); + return MZ_FALSE; + } + + if (pState->m_central_dir.m_size > 0xFFFFFFFF) + return MZ_FALSE; + n = (mz_uint32)orig_central_dir_size; + if (!mz_zip_array_push_back(pZip, &pState->m_central_dir_offsets, &n, 1)) + { + mz_zip_array_resize(pZip, &pState->m_central_dir, orig_central_dir_size, MZ_FALSE); + return MZ_FALSE; + } + + pZip->m_total_files++; + pZip->m_archive_size = cur_dst_file_ofs; + + return MZ_TRUE; +} + +mz_bool mz_zip_writer_finalize_archive(mz_zip_archive *pZip) +{ + mz_zip_internal_state *pState; + mz_uint64 central_dir_ofs, central_dir_size; + mz_uint8 hdr[MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE]; + + if ((!pZip) || (!pZip->m_pState) || (pZip->m_zip_mode != MZ_ZIP_MODE_WRITING)) + return MZ_FALSE; + + pState = pZip->m_pState; + + // no zip64 support yet + if ((pZip->m_total_files > 0xFFFF) || ((pZip->m_archive_size + pState->m_central_dir.m_size + MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE) > 0xFFFFFFFF)) + return MZ_FALSE; + + central_dir_ofs = 0; + central_dir_size = 0; + if (pZip->m_total_files) + { + // Write central directory + central_dir_ofs = pZip->m_archive_size; + central_dir_size = pState->m_central_dir.m_size; + pZip->m_central_directory_file_ofs = central_dir_ofs; + if (pZip->m_pWrite(pZip->m_pIO_opaque, central_dir_ofs, pState->m_central_dir.m_p, (size_t)central_dir_size) != central_dir_size) + return MZ_FALSE; + pZip->m_archive_size += central_dir_size; + } + + // Write end of central directory record + MZ_CLEAR_OBJ(hdr); + MZ_WRITE_LE32(hdr + MZ_ZIP_ECDH_SIG_OFS, MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIG); + MZ_WRITE_LE16(hdr + MZ_ZIP_ECDH_CDIR_NUM_ENTRIES_ON_DISK_OFS, pZip->m_total_files); + MZ_WRITE_LE16(hdr + MZ_ZIP_ECDH_CDIR_TOTAL_ENTRIES_OFS, pZip->m_total_files); + MZ_WRITE_LE32(hdr + MZ_ZIP_ECDH_CDIR_SIZE_OFS, central_dir_size); + MZ_WRITE_LE32(hdr + MZ_ZIP_ECDH_CDIR_OFS_OFS, central_dir_ofs); + + if (pZip->m_pWrite(pZip->m_pIO_opaque, pZip->m_archive_size, hdr, sizeof(hdr)) != sizeof(hdr)) + return MZ_FALSE; +#ifndef MINIZ_NO_STDIO + if ((pState->m_pFile) && (MZ_FFLUSH(pState->m_pFile) == EOF)) + return MZ_FALSE; +#endif // #ifndef MINIZ_NO_STDIO + + pZip->m_archive_size += sizeof(hdr); + + pZip->m_zip_mode = MZ_ZIP_MODE_WRITING_HAS_BEEN_FINALIZED; + return MZ_TRUE; +} + +mz_bool mz_zip_writer_finalize_heap_archive(mz_zip_archive *pZip, void **pBuf, size_t *pSize) +{ + if ((!pZip) || (!pZip->m_pState) || (!pBuf) || (!pSize)) + return MZ_FALSE; + if (pZip->m_pWrite != mz_zip_heap_write_func) + return MZ_FALSE; + if (!mz_zip_writer_finalize_archive(pZip)) + return MZ_FALSE; + + *pBuf = pZip->m_pState->m_pMem; + *pSize = pZip->m_pState->m_mem_size; + pZip->m_pState->m_pMem = NULL; + pZip->m_pState->m_mem_size = pZip->m_pState->m_mem_capacity = 0; + return MZ_TRUE; +} + +mz_bool mz_zip_writer_end(mz_zip_archive *pZip) +{ + mz_zip_internal_state *pState; + mz_bool status = MZ_TRUE; + if ((!pZip) || (!pZip->m_pState) || (!pZip->m_pAlloc) || (!pZip->m_pFree) || ((pZip->m_zip_mode != MZ_ZIP_MODE_WRITING) && (pZip->m_zip_mode != MZ_ZIP_MODE_WRITING_HAS_BEEN_FINALIZED))) + return MZ_FALSE; + + pState = pZip->m_pState; + pZip->m_pState = NULL; + mz_zip_array_clear(pZip, &pState->m_central_dir); + mz_zip_array_clear(pZip, &pState->m_central_dir_offsets); + mz_zip_array_clear(pZip, &pState->m_sorted_central_dir_offsets); + +#ifndef MINIZ_NO_STDIO + if (pState->m_pFile) + { + MZ_FCLOSE(pState->m_pFile); + pState->m_pFile = NULL; + } +#endif // #ifndef MINIZ_NO_STDIO + + if ((pZip->m_pWrite == mz_zip_heap_write_func) && (pState->m_pMem)) + { + pZip->m_pFree(pZip->m_pAlloc_opaque, pState->m_pMem); + pState->m_pMem = NULL; + } + + pZip->m_pFree(pZip->m_pAlloc_opaque, pState); + pZip->m_zip_mode = MZ_ZIP_MODE_INVALID; + return status; +} + +#ifndef MINIZ_NO_STDIO +mz_bool mz_zip_add_mem_to_archive_file_in_place(const char *pZip_filename, const char *pArchive_name, const void *pBuf, size_t buf_size, const void *pComment, mz_uint16 comment_size, mz_uint level_and_flags) +{ + mz_bool status, created_new_archive = MZ_FALSE; + mz_zip_archive zip_archive; + struct MZ_FILE_STAT_STRUCT file_stat; + MZ_CLEAR_OBJ(zip_archive); + if ((int)level_and_flags < 0) + level_and_flags = MZ_DEFAULT_LEVEL; + if ((!pZip_filename) || (!pArchive_name) || ((buf_size) && (!pBuf)) || ((comment_size) && (!pComment)) || ((level_and_flags & 0xF) > MZ_UBER_COMPRESSION)) + return MZ_FALSE; + if (!mz_zip_writer_validate_archive_name(pArchive_name)) + return MZ_FALSE; + if (MZ_FILE_STAT(pZip_filename, &file_stat) != 0) + { + // Create a new archive. + if (!mz_zip_writer_init_file(&zip_archive, pZip_filename, 0)) + return MZ_FALSE; + created_new_archive = MZ_TRUE; + } + else + { + // Append to an existing archive. + if (!mz_zip_reader_init_file(&zip_archive, pZip_filename, level_and_flags | MZ_ZIP_FLAG_DO_NOT_SORT_CENTRAL_DIRECTORY)) + return MZ_FALSE; + if (!mz_zip_writer_init_from_reader(&zip_archive, pZip_filename)) + { + mz_zip_reader_end(&zip_archive); + return MZ_FALSE; + } + } + status = mz_zip_writer_add_mem_ex(&zip_archive, pArchive_name, pBuf, buf_size, pComment, comment_size, level_and_flags, 0, 0); + // Always finalize, even if adding failed for some reason, so we have a valid central directory. (This may not always succeed, but we can try.) + if (!mz_zip_writer_finalize_archive(&zip_archive)) + status = MZ_FALSE; + if (!mz_zip_writer_end(&zip_archive)) + status = MZ_FALSE; + if ((!status) && (created_new_archive)) + { + // It's a new archive and something went wrong, so just delete it. + int ignoredStatus = MZ_DELETE_FILE(pZip_filename); + (void)ignoredStatus; + } + return status; +} + +void *mz_zip_extract_archive_file_to_heap(const char *pZip_filename, const char *pArchive_name, size_t *pSize, mz_uint flags) +{ + int file_index; + mz_zip_archive zip_archive; + void *p = NULL; + + if (pSize) + *pSize = 0; + + if ((!pZip_filename) || (!pArchive_name)) + return NULL; + + MZ_CLEAR_OBJ(zip_archive); + if (!mz_zip_reader_init_file(&zip_archive, pZip_filename, flags | MZ_ZIP_FLAG_DO_NOT_SORT_CENTRAL_DIRECTORY)) + return NULL; + + if ((file_index = mz_zip_reader_locate_file(&zip_archive, pArchive_name, NULL, flags)) >= 0) + p = mz_zip_reader_extract_to_heap(&zip_archive, file_index, pSize, flags); + + mz_zip_reader_end(&zip_archive); + return p; +} + +#endif // #ifndef MINIZ_NO_STDIO + +#endif // #ifndef MINIZ_NO_ARCHIVE_WRITING_APIS + +#endif // #ifndef MINIZ_NO_ARCHIVE_APIS + +#ifdef __cplusplus +} +#endif + +#endif // MINIZ_HEADER_FILE_ONLY + +/* + This is free and unencumbered software released into the public domain. + + Anyone is free to copy, modify, publish, use, compile, sell, or + distribute this software, either in source code form or as a compiled + binary, for any purpose, commercial or non-commercial, and by any + means. + + In jurisdictions that recognize copyright laws, the author or authors + of this software dedicate any and all copyright interest in the + software to the public domain. We make this dedication for the benefit + of the public at large and to the detriment of our heirs and + successors. We intend this dedication to be an overt act of + relinquishment in perpetuity of all present and future rights to this + software under copyright law. + + 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 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. + + For more information, please refer to +*/ ADDED extsrc/pikchr.c Index: extsrc/pikchr.c ================================================================== --- /dev/null +++ extsrc/pikchr.c @@ -0,0 +1,8086 @@ +/* This file is automatically generated by Lemon from input grammar +** source file "pikchr.y". */ +/* +** Zero-Clause BSD license: +** +** Copyright (C) 2020-09-01 by D. Richard Hipp +** +** Permission to use, copy, modify, and/or distribute this software for +** any purpose with or without fee is hereby granted. +** +**************************************************************************** +** +** This software translates a PIC-inspired diagram language into SVG. +** +** PIKCHR (pronounced like "picture") is *mostly* backwards compatible +** with legacy PIC, though some features of legacy PIC are removed +** (for example, the "sh" command is removed for security) and +** many enhancements are added. +** +** PIKCHR is designed for use in an internet facing web environment. +** In particular, PIKCHR is designed to safely generate benign SVG from +** source text that provided by a hostile agent. +** +** This code was originally written by D. Richard Hipp using documentation +** from prior PIC implementations but without reference to prior code. +** All of the code in this project is original. +** +** This file implements a C-language subroutine that accepts a string +** of PIKCHR language text and generates a second string of SVG output that +** renders the drawing defined by the input. Space to hold the returned +** string is obtained from malloc() and should be freed by the caller. +** NULL might be returned if there is a memory allocation error. +** +** If there are errors in the PIKCHR input, the output will consist of an +** error message and the original PIKCHR input text (inside of
...
). +** +** The subroutine implemented by this file is intended to be stand-alone. +** It uses no external routines other than routines commonly found in +** the standard C library. +** +**************************************************************************** +** COMPILING: +** +** The original source text is a mixture of C99 and "Lemon" +** (See https://sqlite.org/src/file/doc/lemon.html). Lemon is an LALR(1) +** parser generator program, similar to Yacc. The grammar of the +** input language is specified in Lemon. C-code is attached. Lemon +** runs to generate a single output file ("pikchr.c") which is then +** compiled to generate the Pikchr library. This header comment is +** preserved in the Lemon output, so you might be reading this in either +** the generated "pikchr.c" file that is output by Lemon, or in the +** "pikchr.y" source file that is input into Lemon. If you make changes, +** you should change the input source file "pikchr.y", not the +** Lemon-generated output file. +** +** Basic compilation steps: +** +** lemon pikchr.y +** cc pikchr.c -o pikchr.o +** +** Add -DPIKCHR_SHELL to add a main() routine that reads input files +** and sends them through Pikchr, for testing. Add -DPIKCHR_FUZZ for +** -fsanitizer=fuzzer testing. +** +**************************************************************************** +** IMPLEMENTATION NOTES (for people who want to understand the internal +** operation of this software, perhaps to extend the code or to fix bugs): +** +** Each call to pikchr() uses a single instance of the Pik structure to +** track its internal state. The Pik structure lives for the duration +** of the pikchr() call. +** +** The input is a sequence of objects or "statements". Each statement is +** parsed into a PObj object. These are stored on an extensible array +** called PList. All parameters to each PObj are computed as the +** object is parsed. (Hence, the parameters to a PObj may only refer +** to prior statements.) Once the PObj is completely assembled, it is +** added to the end of a PList and never changes thereafter - except, +** PObj objects that are part of a "[...]" block might have their +** absolute position shifted when the outer [...] block is positioned. +** But apart from this repositioning, PObj objects are unchanged once +** they are added to the list. The order of statements on a PList does +** not change. +** +** After all input has been parsed, the top-level PList is walked to +** generate output. Sub-lists resulting from [...] blocks are scanned +** as they are encountered. All input must be collected and parsed ahead +** of output generation because the size and position of statements must be +** known in order to compute a bounding box on the output. +** +** Each PObj is on a "layer". (The common case is that all PObj's are +** on a single layer, but multiple layers are possible.) A separate pass +** is made through the list for each layer. +** +** After all output is generated, the Pik object and all the PList +** and PObj objects are deallocated and the generated output string is +** returned. Upon any error, the Pik.nErr flag is set, processing quickly +** stops, and the stack unwinds. No attempt is made to continue reading +** input after an error. +** +** Most statements begin with a class name like "box" or "arrow" or "move". +** There is a class named "text" which is used for statements that begin +** with a string literal. You can also specify the "text" class. +** A Sublist ("[...]") is a single object that contains a pointer to +** its substatements, all gathered onto a separate PList object. +** +** Variables go into PVar objects that form a linked list. +** +** Each PObj has zero or one names. Input constructs that attempt +** to assign a new name from an older name, for example: +** +** Abc: Abc + (0.5cm, 0) +** +** Statements like these generate a new "noop" object at the specified +** place and with the given name. As place-names are searched by scanning +** the list in reverse order, this has the effect of overriding the "Abc" +** name when referenced by subsequent objects. +*/ +#include +#include +#include +#include +#include +#include +#define count(X) (sizeof(X)/sizeof(X[0])) +#ifndef M_PI +# define M_PI 3.1415926535897932385 +#endif + +/* Tag intentionally unused parameters with this macro to prevent +** compiler warnings with -Wextra */ +#define UNUSED_PARAMETER(X) (void)(X) + +typedef struct Pik Pik; /* Complete parsing context */ +typedef struct PToken PToken; /* A single token */ +typedef struct PObj PObj; /* A single diagram object */ +typedef struct PList PList; /* A list of diagram objects */ +typedef struct PClass PClass; /* Description of statements types */ +typedef double PNum; /* Numeric value */ +typedef struct PRel PRel; /* Absolute or percentage value */ +typedef struct PPoint PPoint; /* A position in 2-D space */ +typedef struct PVar PVar; /* script-defined variable */ +typedef struct PBox PBox; /* A bounding box */ +typedef struct PMacro PMacro; /* A "define" macro */ + +/* Compass points */ +#define CP_N 1 +#define CP_NE 2 +#define CP_E 3 +#define CP_SE 4 +#define CP_S 5 +#define CP_SW 6 +#define CP_W 7 +#define CP_NW 8 +#define CP_C 9 /* .center or .c */ +#define CP_END 10 /* .end */ +#define CP_START 11 /* .start */ + +/* Heading angles corresponding to compass points */ +static const PNum pik_hdg_angle[] = { +/* none */ 0.0, + /* N */ 0.0, + /* NE */ 45.0, + /* E */ 90.0, + /* SE */ 135.0, + /* S */ 180.0, + /* SW */ 225.0, + /* W */ 270.0, + /* NW */ 315.0, + /* C */ 0.0, +}; + +/* Built-in functions */ +#define FN_ABS 0 +#define FN_COS 1 +#define FN_INT 2 +#define FN_MAX 3 +#define FN_MIN 4 +#define FN_SIN 5 +#define FN_SQRT 6 + +/* Text position and style flags. Stored in PToken.eCode so limited +** to 15 bits. */ +#define TP_LJUST 0x0001 /* left justify...... */ +#define TP_RJUST 0x0002 /* ...Right justify */ +#define TP_JMASK 0x0003 /* Mask for justification bits */ +#define TP_ABOVE2 0x0004 /* Position text way above PObj.ptAt */ +#define TP_ABOVE 0x0008 /* Position text above PObj.ptAt */ +#define TP_CENTER 0x0010 /* On the line */ +#define TP_BELOW 0x0020 /* Position text below PObj.ptAt */ +#define TP_BELOW2 0x0040 /* Position text way below PObj.ptAt */ +#define TP_VMASK 0x007c /* Mask for text positioning flags */ +#define TP_BIG 0x0100 /* Larger font */ +#define TP_SMALL 0x0200 /* Smaller font */ +#define TP_XTRA 0x0400 /* Amplify TP_BIG or TP_SMALL */ +#define TP_SZMASK 0x0700 /* Font size mask */ +#define TP_ITALIC 0x1000 /* Italic font */ +#define TP_BOLD 0x2000 /* Bold font */ +#define TP_FMASK 0x3000 /* Mask for font style */ +#define TP_ALIGN 0x4000 /* Rotate to align with the line */ + +/* An object to hold a position in 2-D space */ +struct PPoint { + PNum x, y; /* X and Y coordinates */ +}; +static const PPoint cZeroPoint = {0.0,0.0}; + +/* A bounding box */ +struct PBox { + PPoint sw, ne; /* Lower-left and top-right corners */ +}; + +/* An Absolute or a relative distance. The absolute distance +** is stored in rAbs and the relative distance is stored in rRel. +** Usually, one or the other will be 0.0. When using a PRel to +** update an existing value, the computation is usually something +** like this: +** +** value = PRel.rAbs + value*PRel.rRel +** +*/ +struct PRel { + PNum rAbs; /* Absolute value */ + PNum rRel; /* Value relative to current value */ +}; + +/* A variable created by the ID = EXPR construct of the PIKCHR script +** +** PIKCHR (and PIC) scripts do not use many varaibles, so it is reasonable +** to store them all on a linked list. +*/ +struct PVar { + const char *zName; /* Name of the variable */ + PNum val; /* Value of the variable */ + PVar *pNext; /* Next variable in a list of them all */ +}; + +/* A single token in the parser input stream +*/ +struct PToken { + const char *z; /* Pointer to the token text */ + unsigned int n; /* Length of the token in bytes */ + short int eCode; /* Auxiliary code */ + unsigned char eType; /* The numeric parser code */ + unsigned char eEdge; /* Corner value for corner keywords */ +}; + +/* Return negative, zero, or positive if pToken is less than, equal to +** or greater than the zero-terminated string z[] +*/ +static int pik_token_eq(PToken *pToken, const char *z){ + int c = strncmp(pToken->z,z,pToken->n); + if( c==0 && z[pToken->n]!=0 ) c = -1; + return c; +} + +/* Extra token types not generated by LEMON but needed by the +** tokenizer +*/ +#define T_PARAMETER 253 /* $1, $2, ..., $9 */ +#define T_WHITESPACE 254 /* Whitespace of comments */ +#define T_ERROR 255 /* Any text that is not a valid token */ + +/* Directions of movement */ +#define DIR_RIGHT 0 +#define DIR_DOWN 1 +#define DIR_LEFT 2 +#define DIR_UP 3 +#define ValidDir(X) ((X)>=0 && (X)<=3) +#define IsUpDown(X) (((X)&1)==1) +#define IsLeftRight(X) (((X)&1)==0) + +/* Bitmask for the various attributes for PObj. These bits are +** collected in PObj.mProp and PObj.mCalc to check for constraint +** errors. */ +#define A_WIDTH 0x0001 +#define A_HEIGHT 0x0002 +#define A_RADIUS 0x0004 +#define A_THICKNESS 0x0008 +#define A_DASHED 0x0010 /* Includes "dotted" */ +#define A_FILL 0x0020 +#define A_COLOR 0x0040 +#define A_ARROW 0x0080 +#define A_FROM 0x0100 +#define A_CW 0x0200 +#define A_AT 0x0400 +#define A_TO 0x0800 /* one or more movement attributes */ +#define A_FIT 0x1000 + + +/* A single graphics object */ +struct PObj { + const PClass *type; /* Object type or class */ + PToken errTok; /* Reference token for error messages */ + PPoint ptAt; /* Reference point for the object */ + PPoint ptEnter, ptExit; /* Entry and exit points */ + PList *pSublist; /* Substructure for [...] objects */ + char *zName; /* Name assigned to this statement */ + PNum w; /* "width" property */ + PNum h; /* "height" property */ + PNum rad; /* "radius" property */ + PNum sw; /* "thickness" property. (Mnemonic: "stroke width")*/ + PNum dotted; /* "dotted" property. <=0.0 for off */ + PNum dashed; /* "dashed" property. <=0.0 for off */ + PNum fill; /* "fill" property. Negative for off */ + PNum color; /* "color" property */ + PPoint with; /* Position constraint from WITH clause */ + char eWith; /* Type of heading point on WITH clause */ + char cw; /* True for clockwise arc */ + char larrow; /* Arrow at beginning (<- or <->) */ + char rarrow; /* Arrow at end (-> or <->) */ + char bClose; /* True if "close" is seen */ + char bChop; /* True if "chop" is seen */ + unsigned char nTxt; /* Number of text values */ + unsigned mProp; /* Masks of properties set so far */ + unsigned mCalc; /* Values computed from other constraints */ + PToken aTxt[5]; /* Text with .eCode holding TP flags */ + int iLayer; /* Rendering order */ + int inDir, outDir; /* Entry and exit directions */ + int nPath; /* Number of path points */ + PPoint *aPath; /* Array of path points */ + PObj *pFrom, *pTo; /* End-point objects of a path */ + PBox bbox; /* Bounding box */ +}; + +/* A list of graphics objects */ +struct PList { + int n; /* Number of statements in the list */ + int nAlloc; /* Allocated slots in a[] */ + PObj **a; /* Pointers to individual objects */ +}; + +/* A macro definition */ +struct PMacro { + PMacro *pNext; /* Next in the list */ + PToken macroName; /* Name of the macro */ + PToken macroBody; /* Body of the macro */ + int inUse; /* Do not allow recursion */ +}; + +/* Each call to the pikchr() subroutine uses an instance of the following +** object to pass around context to all of its subroutines. +*/ +struct Pik { + unsigned nErr; /* Number of errors seen */ + PToken sIn; /* Input Pikchr-language text */ + char *zOut; /* Result accumulates here */ + unsigned int nOut; /* Bytes written to zOut[] so far */ + unsigned int nOutAlloc; /* Space allocated to zOut[] */ + unsigned char eDir; /* Current direction */ + unsigned int mFlags; /* Flags passed to pikchr() */ + PObj *cur; /* Object under construction */ + PObj *lastRef; /* Last object references by name */ + PList *list; /* Object list under construction */ + PMacro *pMacros; /* List of all defined macros */ + PVar *pVar; /* Application-defined variables */ + PBox bbox; /* Bounding box around all statements */ + /* Cache of layout values. <=0.0 for unknown... */ + PNum rScale; /* Multiply to convert inches to pixels */ + PNum fontScale; /* Scale fonts by this percent */ + PNum charWidth; /* Character width */ + PNum charHeight; /* Character height */ + PNum wArrow; /* Width of arrowhead at the fat end */ + PNum hArrow; /* Ht of arrowhead - dist from tip to fat end */ + char bLayoutVars; /* True if cache is valid */ + char thenFlag; /* True if "then" seen */ + char samePath; /* aTPath copied by "same" */ + const char *zClass; /* Class name for the */ + int wSVG, hSVG; /* Width and height of the */ + int fgcolor; /* foreground color value, or -1 for none */ + int bgcolor; /* background color value, or -1 for none */ + /* Paths for lines are constructed here first, then transferred into + ** the PObj object at the end: */ + int nTPath; /* Number of entries on aTPath[] */ + int mTPath; /* For last entry, 1: x set, 2: y set */ + PPoint aTPath[1000]; /* Path under construction */ + /* Error contexts */ + unsigned int nCtx; /* Number of error contexts */ + PToken aCtx[10]; /* Nested error contexts */ +}; + +/* Include PIKCHR_PLAINTEXT_ERRORS among the bits of mFlags on the 3rd +** argument to pikchr() in order to cause error message text to come out +** as text/plain instead of as text/html +*/ +#define PIKCHR_PLAINTEXT_ERRORS 0x0001 + +/* Include PIKCHR_DARK_MODE among the mFlag bits to invert colors. +*/ +#define PIKCHR_DARK_MODE 0x0002 + +/* +** The behavior of an object class is defined by an instance of +** this structure. This is the "virtual method" table. +*/ +struct PClass { + const char *zName; /* Name of class */ + char isLine; /* True if a line class */ + char eJust; /* Use box-style text justification */ + void (*xInit)(Pik*,PObj*); /* Initializer */ + void (*xNumProp)(Pik*,PObj*,PToken*); /* Value change notification */ + void (*xCheck)(Pik*,PObj*); /* Checks to do after parsing */ + PPoint (*xChop)(Pik*,PObj*,PPoint*); /* Chopper */ + PPoint (*xOffset)(Pik*,PObj*,int); /* Offset from .c to edge point */ + void (*xFit)(Pik*,PObj*,PNum w,PNum h); /* Size to fit text */ + void (*xRender)(Pik*,PObj*); /* Render */ +}; + + +/* Forward declarations */ +static void pik_append(Pik*, const char*,int); +static void pik_append_text(Pik*,const char*,int,int); +static void pik_append_num(Pik*,const char*,PNum); +static void pik_append_point(Pik*,const char*,PPoint*); +static void pik_append_x(Pik*,const char*,PNum,const char*); +static void pik_append_y(Pik*,const char*,PNum,const char*); +static void pik_append_xy(Pik*,const char*,PNum,PNum); +static void pik_append_dis(Pik*,const char*,PNum,const char*); +static void pik_append_arc(Pik*,PNum,PNum,PNum,PNum); +static void pik_append_clr(Pik*,const char*,PNum,const char*,int); +static void pik_append_style(Pik*,PObj*,int); +static void pik_append_txt(Pik*,PObj*, PBox*); +static void pik_draw_arrowhead(Pik*,PPoint*pFrom,PPoint*pTo,PObj*); +static void pik_chop(PPoint*pFrom,PPoint*pTo,PNum); +static void pik_error(Pik*,PToken*,const char*); +static void pik_elist_free(Pik*,PList*); +static void pik_elem_free(Pik*,PObj*); +static void pik_render(Pik*,PList*); +static PList *pik_elist_append(Pik*,PList*,PObj*); +static PObj *pik_elem_new(Pik*,PToken*,PToken*,PList*); +static void pik_set_direction(Pik*,int); +static void pik_elem_setname(Pik*,PObj*,PToken*); +static int pik_round(PNum); +static void pik_set_var(Pik*,PToken*,PNum,PToken*); +static PNum pik_value(Pik*,const char*,int,int*); +static int pik_value_int(Pik*,const char*,int,int*); +static PNum pik_lookup_color(Pik*,PToken*); +static PNum pik_get_var(Pik*,PToken*); +static PNum pik_atof(PToken*); +static void pik_after_adding_attributes(Pik*,PObj*); +static void pik_elem_move(PObj*,PNum dx, PNum dy); +static void pik_elist_move(PList*,PNum dx, PNum dy); +static void pik_set_numprop(Pik*,PToken*,PRel*); +static void pik_set_clrprop(Pik*,PToken*,PNum); +static void pik_set_dashed(Pik*,PToken*,PNum*); +static void pik_then(Pik*,PToken*,PObj*); +static void pik_add_direction(Pik*,PToken*,PRel*); +static void pik_move_hdg(Pik*,PRel*,PToken*,PNum,PToken*,PToken*); +static void pik_evenwith(Pik*,PToken*,PPoint*); +static void pik_set_from(Pik*,PObj*,PToken*,PPoint*); +static void pik_add_to(Pik*,PObj*,PToken*,PPoint*); +static void pik_close_path(Pik*,PToken*); +static void pik_set_at(Pik*,PToken*,PPoint*,PToken*); +static short int pik_nth_value(Pik*,PToken*); +static PObj *pik_find_nth(Pik*,PObj*,PToken*); +static PObj *pik_find_byname(Pik*,PObj*,PToken*); +static PPoint pik_place_of_elem(Pik*,PObj*,PToken*); +static int pik_bbox_isempty(PBox*); +static int pik_bbox_contains_point(PBox*,PPoint*); +static void pik_bbox_init(PBox*); +static void pik_bbox_addbox(PBox*,PBox*); +static void pik_bbox_add_xy(PBox*,PNum,PNum); +static void pik_bbox_addellipse(PBox*,PNum x,PNum y,PNum rx,PNum ry); +static void pik_add_txt(Pik*,PToken*,int); +static int pik_text_length(const PToken *pToken); +static void pik_size_to_fit(Pik*,PToken*,int); +static int pik_text_position(int,PToken*); +static PNum pik_property_of(PObj*,PToken*); +static PNum pik_func(Pik*,PToken*,PNum,PNum); +static PPoint pik_position_between(PNum x, PPoint p1, PPoint p2); +static PPoint pik_position_at_angle(PNum dist, PNum r, PPoint pt); +static PPoint pik_position_at_hdg(PNum dist, PToken *pD, PPoint pt); +static void pik_same(Pik *p, PObj*, PToken*); +static PPoint pik_nth_vertex(Pik *p, PToken *pNth, PToken *pErr, PObj *pObj); +static PToken pik_next_semantic_token(PToken *pThis); +static void pik_compute_layout_settings(Pik*); +static void pik_behind(Pik*,PObj*); +static PObj *pik_assert(Pik*,PNum,PToken*,PNum); +static PObj *pik_position_assert(Pik*,PPoint*,PToken*,PPoint*); +static PNum pik_dist(PPoint*,PPoint*); +static void pik_add_macro(Pik*,PToken *pId,PToken *pCode); + + +#line 510 "pikchr.c" +/**************** End of %include directives **********************************/ +/* These constants specify the various numeric values for terminal symbols. +***************** Begin token definitions *************************************/ +#ifndef T_ID +#define T_ID 1 +#define T_EDGEPT 2 +#define T_OF 3 +#define T_PLUS 4 +#define T_MINUS 5 +#define T_STAR 6 +#define T_SLASH 7 +#define T_PERCENT 8 +#define T_UMINUS 9 +#define T_EOL 10 +#define T_ASSIGN 11 +#define T_PLACENAME 12 +#define T_COLON 13 +#define T_ASSERT 14 +#define T_LP 15 +#define T_EQ 16 +#define T_RP 17 +#define T_DEFINE 18 +#define T_CODEBLOCK 19 +#define T_FILL 20 +#define T_COLOR 21 +#define T_THICKNESS 22 +#define T_PRINT 23 +#define T_STRING 24 +#define T_COMMA 25 +#define T_CLASSNAME 26 +#define T_LB 27 +#define T_RB 28 +#define T_UP 29 +#define T_DOWN 30 +#define T_LEFT 31 +#define T_RIGHT 32 +#define T_CLOSE 33 +#define T_CHOP 34 +#define T_FROM 35 +#define T_TO 36 +#define T_THEN 37 +#define T_HEADING 38 +#define T_GO 39 +#define T_AT 40 +#define T_WITH 41 +#define T_SAME 42 +#define T_AS 43 +#define T_FIT 44 +#define T_BEHIND 45 +#define T_UNTIL 46 +#define T_EVEN 47 +#define T_DOT_E 48 +#define T_HEIGHT 49 +#define T_WIDTH 50 +#define T_RADIUS 51 +#define T_DIAMETER 52 +#define T_DOTTED 53 +#define T_DASHED 54 +#define T_CW 55 +#define T_CCW 56 +#define T_LARROW 57 +#define T_RARROW 58 +#define T_LRARROW 59 +#define T_INVIS 60 +#define T_THICK 61 +#define T_THIN 62 +#define T_SOLID 63 +#define T_CENTER 64 +#define T_LJUST 65 +#define T_RJUST 66 +#define T_ABOVE 67 +#define T_BELOW 68 +#define T_ITALIC 69 +#define T_BOLD 70 +#define T_ALIGNED 71 +#define T_BIG 72 +#define T_SMALL 73 +#define T_AND 74 +#define T_LT 75 +#define T_GT 76 +#define T_ON 77 +#define T_WAY 78 +#define T_BETWEEN 79 +#define T_THE 80 +#define T_NTH 81 +#define T_VERTEX 82 +#define T_TOP 83 +#define T_BOTTOM 84 +#define T_START 85 +#define T_END 86 +#define T_IN 87 +#define T_THIS 88 +#define T_DOT_U 89 +#define T_LAST 90 +#define T_NUMBER 91 +#define T_FUNC1 92 +#define T_FUNC2 93 +#define T_DIST 94 +#define T_DOT_XY 95 +#define T_X 96 +#define T_Y 97 +#define T_DOT_L 98 +#endif +/**************** End token definitions ***************************************/ + +/* The next sections is a series of control #defines. +** various aspects of the generated parser. +** YYCODETYPE is the data type used to store the integer codes +** that represent terminal and non-terminal symbols. +** "unsigned char" is used if there are fewer than +** 256 symbols. Larger types otherwise. +** YYNOCODE is a number of type YYCODETYPE that is not used for +** any terminal or nonterminal symbol. +** YYFALLBACK If defined, this indicates that one or more tokens +** (also known as: "terminal symbols") have fall-back +** values which should be used if the original symbol +** would not parse. This permits keywords to sometimes +** be used as identifiers, for example. +** YYACTIONTYPE is the data type used for "action codes" - numbers +** that indicate what to do in response to the next +** token. +** pik_parserTOKENTYPE is the data type used for minor type for terminal +** symbols. Background: A "minor type" is a semantic +** value associated with a terminal or non-terminal +** symbols. For example, for an "ID" terminal symbol, +** the minor type might be the name of the identifier. +** Each non-terminal can have a different minor type. +** Terminal symbols all have the same minor type, though. +** This macros defines the minor type for terminal +** symbols. +** YYMINORTYPE is the data type used for all minor types. +** This is typically a union of many types, one of +** which is pik_parserTOKENTYPE. The entry in the union +** for terminal symbols is called "yy0". +** YYSTACKDEPTH is the maximum depth of the parser's stack. If +** zero the stack is dynamically sized using realloc() +** pik_parserARG_SDECL A static variable declaration for the %extra_argument +** pik_parserARG_PDECL A parameter declaration for the %extra_argument +** pik_parserARG_PARAM Code to pass %extra_argument as a subroutine parameter +** pik_parserARG_STORE Code to store %extra_argument into yypParser +** pik_parserARG_FETCH Code to extract %extra_argument from yypParser +** pik_parserCTX_* As pik_parserARG_ except for %extra_context +** YYERRORSYMBOL is the code number of the error symbol. If not +** defined, then do no error processing. +** YYNSTATE the combined number of states. +** YYNRULE the number of rules in the grammar +** YYNTOKEN Number of terminal symbols +** YY_MAX_SHIFT Maximum value for shift actions +** YY_MIN_SHIFTREDUCE Minimum value for shift-reduce actions +** YY_MAX_SHIFTREDUCE Maximum value for shift-reduce actions +** YY_ERROR_ACTION The yy_action[] code for syntax error +** YY_ACCEPT_ACTION The yy_action[] code for accept +** YY_NO_ACTION The yy_action[] code for no-op +** YY_MIN_REDUCE Minimum value for reduce actions +** YY_MAX_REDUCE Maximum value for reduce actions +*/ +#ifndef INTERFACE +# define INTERFACE 1 +#endif +/************* Begin control #defines *****************************************/ +#define YYCODETYPE unsigned char +#define YYNOCODE 135 +#define YYACTIONTYPE unsigned short int +#define pik_parserTOKENTYPE PToken +typedef union { + int yyinit; + pik_parserTOKENTYPE yy0; + PRel yy10; + PObj* yy36; + PPoint yy79; + PNum yy153; + short int yy164; + PList* yy227; +} YYMINORTYPE; +#ifndef YYSTACKDEPTH +#define YYSTACKDEPTH 100 +#endif +#define pik_parserARG_SDECL +#define pik_parserARG_PDECL +#define pik_parserARG_PARAM +#define pik_parserARG_FETCH +#define pik_parserARG_STORE +#define pik_parserCTX_SDECL Pik *p; +#define pik_parserCTX_PDECL ,Pik *p +#define pik_parserCTX_PARAM ,p +#define pik_parserCTX_FETCH Pik *p=yypParser->p; +#define pik_parserCTX_STORE yypParser->p=p; +#define YYFALLBACK 1 +#define YYNSTATE 164 +#define YYNRULE 156 +#define YYNRULE_WITH_ACTION 116 +#define YYNTOKEN 99 +#define YY_MAX_SHIFT 163 +#define YY_MIN_SHIFTREDUCE 287 +#define YY_MAX_SHIFTREDUCE 442 +#define YY_ERROR_ACTION 443 +#define YY_ACCEPT_ACTION 444 +#define YY_NO_ACTION 445 +#define YY_MIN_REDUCE 446 +#define YY_MAX_REDUCE 601 +/************* End control #defines *******************************************/ +#define YY_NLOOKAHEAD ((int)(sizeof(yy_lookahead)/sizeof(yy_lookahead[0]))) + +/* Define the yytestcase() macro to be a no-op if is not already defined +** otherwise. +** +** Applications can choose to define yytestcase() in the %include section +** to a macro that can assist in verifying code coverage. For production +** code the yytestcase() macro should be turned off. But it is useful +** for testing. +*/ +#ifndef yytestcase +# define yytestcase(X) +#endif + + +/* Next are the tables used to determine what action to take based on the +** current state and lookahead token. These tables are used to implement +** functions that take a state number and lookahead value and return an +** action integer. +** +** Suppose the action integer is N. Then the action is determined as +** follows +** +** 0 <= N <= YY_MAX_SHIFT Shift N. That is, push the lookahead +** token onto the stack and goto state N. +** +** N between YY_MIN_SHIFTREDUCE Shift to an arbitrary state then +** and YY_MAX_SHIFTREDUCE reduce by rule N-YY_MIN_SHIFTREDUCE. +** +** N == YY_ERROR_ACTION A syntax error has occurred. +** +** N == YY_ACCEPT_ACTION The parser accepts its input. +** +** N == YY_NO_ACTION No such action. Denotes unused +** slots in the yy_action[] table. +** +** N between YY_MIN_REDUCE Reduce by rule N-YY_MIN_REDUCE +** and YY_MAX_REDUCE +** +** The action table is constructed as a single large table named yy_action[]. +** Given state S and lookahead X, the action is computed as either: +** +** (A) N = yy_action[ yy_shift_ofst[S] + X ] +** (B) N = yy_default[S] +** +** The (A) formula is preferred. The B formula is used instead if +** yy_lookahead[yy_shift_ofst[S]+X] is not equal to X. +** +** The formulas above are for computing the action when the lookahead is +** a terminal symbol. If the lookahead is a non-terminal (as occurs after +** a reduce action) then the yy_reduce_ofst[] array is used in place of +** the yy_shift_ofst[] array. +** +** The following are the tables generated in this section: +** +** yy_action[] A single table containing all actions. +** yy_lookahead[] A table containing the lookahead for each entry in +** yy_action. Used to detect hash collisions. +** yy_shift_ofst[] For each state, the offset into yy_action for +** shifting terminals. +** yy_reduce_ofst[] For each state, the offset into yy_action for +** shifting non-terminals after a reduce. +** yy_default[] Default action for each state. +** +*********** Begin parsing tables **********************************************/ +#define YY_ACTTAB_COUNT (1303) +static const YYACTIONTYPE yy_action[] = { + /* 0 */ 575, 495, 161, 119, 25, 452, 29, 74, 129, 148, + /* 10 */ 575, 492, 161, 119, 453, 113, 120, 161, 119, 530, + /* 20 */ 427, 428, 339, 559, 81, 30, 560, 561, 575, 64, + /* 30 */ 63, 62, 61, 322, 323, 9, 8, 33, 149, 32, + /* 40 */ 7, 71, 127, 38, 335, 66, 48, 37, 28, 339, + /* 50 */ 339, 339, 339, 425, 426, 340, 341, 342, 343, 344, + /* 60 */ 345, 346, 347, 348, 474, 528, 161, 119, 577, 77, + /* 70 */ 577, 73, 376, 148, 474, 533, 161, 119, 112, 113, + /* 80 */ 120, 161, 119, 128, 427, 428, 339, 357, 81, 531, + /* 90 */ 161, 119, 474, 36, 330, 13, 306, 322, 323, 9, + /* 100 */ 8, 33, 149, 32, 7, 71, 127, 328, 335, 66, + /* 110 */ 579, 310, 31, 339, 339, 339, 339, 425, 426, 340, + /* 120 */ 341, 342, 343, 344, 345, 346, 347, 348, 394, 435, + /* 130 */ 46, 59, 60, 64, 63, 62, 61, 54, 51, 376, + /* 140 */ 69, 108, 2, 47, 403, 83, 297, 435, 375, 84, + /* 150 */ 117, 80, 35, 308, 79, 133, 122, 126, 441, 440, + /* 160 */ 299, 123, 3, 404, 405, 406, 408, 80, 298, 308, + /* 170 */ 79, 4, 411, 412, 413, 414, 441, 440, 350, 350, + /* 180 */ 350, 350, 350, 350, 350, 350, 350, 350, 62, 61, + /* 190 */ 67, 434, 1, 75, 378, 158, 74, 76, 148, 411, + /* 200 */ 412, 413, 414, 124, 113, 120, 161, 119, 106, 434, + /* 210 */ 436, 437, 438, 439, 5, 375, 6, 117, 393, 155, + /* 220 */ 154, 153, 394, 435, 69, 59, 60, 149, 436, 437, + /* 230 */ 438, 439, 535, 376, 398, 399, 2, 424, 427, 428, + /* 240 */ 339, 156, 156, 156, 423, 394, 435, 65, 59, 60, + /* 250 */ 162, 131, 441, 440, 397, 72, 376, 148, 118, 2, + /* 260 */ 380, 157, 125, 113, 120, 161, 119, 339, 339, 339, + /* 270 */ 339, 425, 426, 535, 11, 441, 440, 394, 356, 535, + /* 280 */ 59, 60, 535, 379, 159, 434, 149, 12, 102, 446, + /* 290 */ 432, 42, 138, 14, 435, 139, 301, 302, 303, 36, + /* 300 */ 305, 430, 106, 16, 436, 437, 438, 439, 434, 375, + /* 310 */ 18, 117, 393, 155, 154, 153, 44, 142, 140, 64, + /* 320 */ 63, 62, 61, 441, 440, 106, 19, 436, 437, 438, + /* 330 */ 439, 45, 375, 20, 117, 393, 155, 154, 153, 68, + /* 340 */ 55, 114, 64, 63, 62, 61, 147, 146, 394, 473, + /* 350 */ 359, 59, 60, 43, 23, 391, 434, 106, 26, 376, + /* 360 */ 57, 58, 42, 49, 375, 392, 117, 393, 155, 154, + /* 370 */ 153, 64, 63, 62, 61, 436, 437, 438, 439, 384, + /* 380 */ 382, 383, 22, 21, 377, 473, 160, 70, 39, 445, + /* 390 */ 24, 445, 145, 141, 431, 142, 140, 64, 63, 62, + /* 400 */ 61, 394, 15, 445, 59, 60, 64, 63, 62, 61, + /* 410 */ 391, 445, 376, 445, 445, 42, 445, 445, 55, 391, + /* 420 */ 156, 156, 156, 445, 147, 146, 445, 52, 106, 445, + /* 430 */ 445, 43, 445, 445, 445, 375, 445, 117, 393, 155, + /* 440 */ 154, 153, 445, 394, 143, 445, 59, 60, 64, 63, + /* 450 */ 62, 61, 313, 445, 376, 378, 158, 42, 445, 445, + /* 460 */ 22, 21, 121, 447, 454, 29, 445, 445, 24, 450, + /* 470 */ 145, 141, 431, 142, 140, 64, 63, 62, 61, 445, + /* 480 */ 163, 106, 445, 445, 444, 27, 445, 445, 375, 445, + /* 490 */ 117, 393, 155, 154, 153, 445, 55, 74, 445, 148, + /* 500 */ 445, 445, 147, 146, 497, 113, 120, 161, 119, 43, + /* 510 */ 445, 394, 445, 445, 59, 60, 445, 445, 445, 118, + /* 520 */ 445, 445, 376, 106, 445, 42, 445, 445, 149, 445, + /* 530 */ 375, 445, 117, 393, 155, 154, 153, 445, 22, 21, + /* 540 */ 394, 144, 445, 59, 60, 445, 24, 445, 145, 141, + /* 550 */ 431, 376, 445, 445, 42, 445, 132, 130, 394, 445, + /* 560 */ 445, 59, 60, 109, 447, 454, 29, 445, 445, 376, + /* 570 */ 450, 445, 42, 445, 394, 445, 445, 59, 60, 445, + /* 580 */ 445, 163, 445, 445, 445, 102, 27, 445, 42, 445, + /* 590 */ 445, 106, 445, 64, 63, 62, 61, 445, 375, 445, + /* 600 */ 117, 393, 155, 154, 153, 394, 355, 445, 59, 60, + /* 610 */ 445, 445, 445, 445, 445, 74, 376, 148, 445, 40, + /* 620 */ 106, 445, 496, 113, 120, 161, 119, 375, 445, 117, + /* 630 */ 393, 155, 154, 153, 445, 448, 454, 29, 106, 445, + /* 640 */ 445, 450, 445, 445, 445, 375, 149, 117, 393, 155, + /* 650 */ 154, 153, 163, 445, 106, 445, 445, 27, 445, 445, + /* 660 */ 445, 375, 445, 117, 393, 155, 154, 153, 394, 445, + /* 670 */ 445, 59, 60, 64, 63, 62, 61, 445, 445, 376, + /* 680 */ 445, 445, 41, 445, 445, 106, 354, 64, 63, 62, + /* 690 */ 61, 445, 375, 445, 117, 393, 155, 154, 153, 445, + /* 700 */ 445, 445, 74, 445, 148, 445, 88, 445, 445, 490, + /* 710 */ 113, 120, 161, 119, 445, 120, 161, 119, 17, 74, + /* 720 */ 445, 148, 110, 110, 445, 445, 484, 113, 120, 161, + /* 730 */ 119, 445, 445, 149, 74, 445, 148, 152, 445, 445, + /* 740 */ 445, 483, 113, 120, 161, 119, 445, 445, 106, 445, + /* 750 */ 149, 445, 445, 107, 445, 375, 445, 117, 393, 155, + /* 760 */ 154, 153, 120, 161, 119, 149, 478, 74, 445, 148, + /* 770 */ 445, 88, 445, 445, 480, 113, 120, 161, 119, 445, + /* 780 */ 120, 161, 119, 74, 152, 148, 10, 479, 479, 445, + /* 790 */ 134, 113, 120, 161, 119, 445, 445, 445, 149, 74, + /* 800 */ 445, 148, 152, 445, 445, 445, 517, 113, 120, 161, + /* 810 */ 119, 445, 445, 74, 149, 148, 445, 445, 445, 445, + /* 820 */ 137, 113, 120, 161, 119, 74, 445, 148, 445, 445, + /* 830 */ 149, 445, 525, 113, 120, 161, 119, 445, 74, 445, + /* 840 */ 148, 445, 445, 445, 149, 527, 113, 120, 161, 119, + /* 850 */ 445, 445, 74, 445, 148, 445, 149, 445, 445, 524, + /* 860 */ 113, 120, 161, 119, 74, 445, 148, 445, 445, 149, + /* 870 */ 445, 526, 113, 120, 161, 119, 445, 445, 74, 445, + /* 880 */ 148, 445, 88, 149, 445, 523, 113, 120, 161, 119, + /* 890 */ 445, 120, 161, 119, 74, 149, 148, 85, 111, 111, + /* 900 */ 445, 522, 113, 120, 161, 119, 120, 161, 119, 149, + /* 910 */ 74, 445, 148, 152, 445, 445, 445, 521, 113, 120, + /* 920 */ 161, 119, 445, 445, 74, 149, 148, 445, 152, 445, + /* 930 */ 445, 520, 113, 120, 161, 119, 74, 445, 148, 445, + /* 940 */ 445, 149, 445, 519, 113, 120, 161, 119, 445, 74, + /* 950 */ 445, 148, 445, 445, 445, 149, 150, 113, 120, 161, + /* 960 */ 119, 445, 445, 74, 445, 148, 445, 149, 445, 445, + /* 970 */ 151, 113, 120, 161, 119, 74, 445, 148, 445, 445, + /* 980 */ 149, 445, 136, 113, 120, 161, 119, 445, 445, 74, + /* 990 */ 445, 148, 107, 445, 149, 445, 135, 113, 120, 161, + /* 1000 */ 119, 120, 161, 119, 445, 463, 149, 445, 88, 445, + /* 1010 */ 445, 445, 78, 78, 445, 445, 107, 120, 161, 119, + /* 1020 */ 149, 445, 445, 152, 82, 120, 161, 119, 445, 463, + /* 1030 */ 445, 466, 86, 34, 445, 88, 445, 569, 445, 152, + /* 1040 */ 445, 120, 161, 119, 120, 161, 119, 152, 107, 445, + /* 1050 */ 445, 475, 64, 63, 62, 61, 445, 120, 161, 119, + /* 1060 */ 98, 451, 445, 152, 89, 396, 152, 90, 445, 120, + /* 1070 */ 161, 119, 445, 120, 161, 119, 120, 161, 119, 152, + /* 1080 */ 445, 64, 63, 62, 61, 445, 445, 445, 445, 445, + /* 1090 */ 87, 152, 445, 99, 395, 152, 100, 445, 152, 120, + /* 1100 */ 161, 119, 120, 161, 119, 120, 161, 119, 445, 101, + /* 1110 */ 64, 63, 62, 61, 445, 445, 445, 445, 120, 161, + /* 1120 */ 119, 152, 91, 391, 152, 445, 445, 152, 103, 445, + /* 1130 */ 445, 120, 161, 119, 445, 92, 445, 120, 161, 119, + /* 1140 */ 152, 93, 445, 445, 120, 161, 119, 104, 445, 445, + /* 1150 */ 120, 161, 119, 152, 445, 445, 120, 161, 119, 152, + /* 1160 */ 445, 445, 445, 445, 94, 445, 152, 445, 445, 445, + /* 1170 */ 105, 445, 152, 120, 161, 119, 445, 95, 152, 120, + /* 1180 */ 161, 119, 96, 445, 445, 445, 120, 161, 119, 445, + /* 1190 */ 445, 120, 161, 119, 97, 152, 445, 445, 445, 445, + /* 1200 */ 549, 152, 445, 120, 161, 119, 548, 445, 152, 120, + /* 1210 */ 161, 119, 445, 152, 445, 120, 161, 119, 445, 445, + /* 1220 */ 445, 445, 445, 547, 445, 152, 445, 445, 445, 445, + /* 1230 */ 445, 152, 120, 161, 119, 546, 445, 152, 445, 115, + /* 1240 */ 445, 445, 116, 445, 120, 161, 119, 445, 120, 161, + /* 1250 */ 119, 120, 161, 119, 152, 64, 63, 62, 61, 64, + /* 1260 */ 63, 62, 61, 445, 445, 445, 152, 445, 445, 445, + /* 1270 */ 152, 445, 445, 152, 445, 445, 50, 445, 445, 445, + /* 1280 */ 53, 64, 63, 62, 61, 445, 445, 445, 445, 445, + /* 1290 */ 445, 445, 445, 445, 445, 445, 445, 445, 445, 445, + /* 1300 */ 445, 445, 56, +}; +static const YYCODETYPE yy_lookahead[] = { + /* 0 */ 0, 112, 113, 114, 133, 101, 102, 103, 105, 105, + /* 10 */ 10, 112, 113, 114, 110, 111, 112, 113, 114, 105, + /* 20 */ 20, 21, 22, 104, 24, 125, 107, 108, 28, 4, + /* 30 */ 5, 6, 7, 33, 34, 35, 36, 37, 134, 39, + /* 40 */ 40, 41, 42, 104, 44, 45, 107, 108, 106, 49, + /* 50 */ 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, + /* 60 */ 60, 61, 62, 63, 0, 112, 113, 114, 129, 130, + /* 70 */ 131, 103, 12, 105, 10, 112, 113, 114, 110, 111, + /* 80 */ 112, 113, 114, 105, 20, 21, 22, 17, 24, 112, + /* 90 */ 113, 114, 28, 10, 2, 25, 25, 33, 34, 35, + /* 100 */ 36, 37, 134, 39, 40, 41, 42, 2, 44, 45, + /* 110 */ 132, 28, 127, 49, 50, 51, 52, 53, 54, 55, + /* 120 */ 56, 57, 58, 59, 60, 61, 62, 63, 1, 2, + /* 130 */ 38, 4, 5, 4, 5, 6, 7, 4, 5, 12, + /* 140 */ 3, 81, 15, 38, 1, 115, 17, 2, 88, 115, + /* 150 */ 90, 24, 128, 26, 27, 12, 1, 14, 31, 32, + /* 160 */ 19, 18, 16, 20, 21, 22, 23, 24, 17, 26, + /* 170 */ 27, 15, 29, 30, 31, 32, 31, 32, 64, 65, + /* 180 */ 66, 67, 68, 69, 70, 71, 72, 73, 6, 7, + /* 190 */ 43, 64, 13, 48, 26, 27, 103, 48, 105, 29, + /* 200 */ 30, 31, 32, 110, 111, 112, 113, 114, 81, 64, + /* 210 */ 83, 84, 85, 86, 40, 88, 40, 90, 91, 92, + /* 220 */ 93, 94, 1, 2, 87, 4, 5, 134, 83, 84, + /* 230 */ 85, 86, 48, 12, 96, 97, 15, 41, 20, 21, + /* 240 */ 22, 20, 21, 22, 41, 1, 2, 98, 4, 5, + /* 250 */ 82, 47, 31, 32, 17, 103, 12, 105, 90, 15, + /* 260 */ 26, 27, 110, 111, 112, 113, 114, 49, 50, 51, + /* 270 */ 52, 53, 54, 89, 25, 31, 32, 1, 17, 95, + /* 280 */ 4, 5, 98, 26, 27, 64, 134, 74, 12, 0, + /* 290 */ 79, 15, 78, 3, 2, 80, 20, 21, 22, 10, + /* 300 */ 24, 79, 81, 3, 83, 84, 85, 86, 64, 88, + /* 310 */ 3, 90, 91, 92, 93, 94, 38, 2, 3, 4, + /* 320 */ 5, 6, 7, 31, 32, 81, 3, 83, 84, 85, + /* 330 */ 86, 16, 88, 3, 90, 91, 92, 93, 94, 3, + /* 340 */ 25, 95, 4, 5, 6, 7, 31, 32, 1, 2, + /* 350 */ 76, 4, 5, 38, 25, 17, 64, 81, 15, 12, + /* 360 */ 15, 15, 15, 25, 88, 17, 90, 91, 92, 93, + /* 370 */ 94, 4, 5, 6, 7, 83, 84, 85, 86, 28, + /* 380 */ 28, 28, 67, 68, 12, 38, 89, 3, 11, 135, + /* 390 */ 75, 135, 77, 78, 79, 2, 3, 4, 5, 6, + /* 400 */ 7, 1, 35, 135, 4, 5, 4, 5, 6, 7, + /* 410 */ 17, 135, 12, 135, 135, 15, 135, 135, 25, 17, + /* 420 */ 20, 21, 22, 135, 31, 32, 135, 25, 81, 135, + /* 430 */ 135, 38, 135, 135, 135, 88, 135, 90, 91, 92, + /* 440 */ 93, 94, 135, 1, 2, 135, 4, 5, 4, 5, + /* 450 */ 6, 7, 8, 135, 12, 26, 27, 15, 135, 135, + /* 460 */ 67, 68, 99, 100, 101, 102, 135, 135, 75, 106, + /* 470 */ 77, 78, 79, 2, 3, 4, 5, 6, 7, 135, + /* 480 */ 117, 81, 135, 135, 121, 122, 135, 135, 88, 135, + /* 490 */ 90, 91, 92, 93, 94, 135, 25, 103, 135, 105, + /* 500 */ 135, 135, 31, 32, 110, 111, 112, 113, 114, 38, + /* 510 */ 135, 1, 135, 135, 4, 5, 135, 135, 135, 90, + /* 520 */ 135, 135, 12, 81, 135, 15, 135, 135, 134, 135, + /* 530 */ 88, 135, 90, 91, 92, 93, 94, 135, 67, 68, + /* 540 */ 1, 2, 135, 4, 5, 135, 75, 135, 77, 78, + /* 550 */ 79, 12, 135, 135, 15, 135, 46, 47, 1, 135, + /* 560 */ 135, 4, 5, 99, 100, 101, 102, 135, 135, 12, + /* 570 */ 106, 135, 15, 135, 1, 135, 135, 4, 5, 135, + /* 580 */ 135, 117, 135, 135, 135, 12, 122, 135, 15, 135, + /* 590 */ 135, 81, 135, 4, 5, 6, 7, 135, 88, 135, + /* 600 */ 90, 91, 92, 93, 94, 1, 17, 135, 4, 5, + /* 610 */ 135, 135, 135, 135, 135, 103, 12, 105, 135, 15, + /* 620 */ 81, 135, 110, 111, 112, 113, 114, 88, 135, 90, + /* 630 */ 91, 92, 93, 94, 135, 100, 101, 102, 81, 135, + /* 640 */ 135, 106, 135, 135, 135, 88, 134, 90, 91, 92, + /* 650 */ 93, 94, 117, 135, 81, 135, 135, 122, 135, 135, + /* 660 */ 135, 88, 135, 90, 91, 92, 93, 94, 1, 135, + /* 670 */ 135, 4, 5, 4, 5, 6, 7, 135, 135, 12, + /* 680 */ 135, 135, 15, 135, 135, 81, 17, 4, 5, 6, + /* 690 */ 7, 135, 88, 135, 90, 91, 92, 93, 94, 135, + /* 700 */ 135, 135, 103, 135, 105, 135, 103, 135, 135, 110, + /* 710 */ 111, 112, 113, 114, 135, 112, 113, 114, 35, 103, + /* 720 */ 135, 105, 119, 120, 135, 135, 110, 111, 112, 113, + /* 730 */ 114, 135, 135, 134, 103, 135, 105, 134, 135, 135, + /* 740 */ 135, 110, 111, 112, 113, 114, 135, 135, 81, 135, + /* 750 */ 134, 135, 135, 103, 135, 88, 135, 90, 91, 92, + /* 760 */ 93, 94, 112, 113, 114, 134, 116, 103, 135, 105, + /* 770 */ 135, 103, 135, 135, 110, 111, 112, 113, 114, 135, + /* 780 */ 112, 113, 114, 103, 134, 105, 118, 119, 120, 135, + /* 790 */ 110, 111, 112, 113, 114, 135, 135, 135, 134, 103, + /* 800 */ 135, 105, 134, 135, 135, 135, 110, 111, 112, 113, + /* 810 */ 114, 135, 135, 103, 134, 105, 135, 135, 135, 135, + /* 820 */ 110, 111, 112, 113, 114, 103, 135, 105, 135, 135, + /* 830 */ 134, 135, 110, 111, 112, 113, 114, 135, 103, 135, + /* 840 */ 105, 135, 135, 135, 134, 110, 111, 112, 113, 114, + /* 850 */ 135, 135, 103, 135, 105, 135, 134, 135, 135, 110, + /* 860 */ 111, 112, 113, 114, 103, 135, 105, 135, 135, 134, + /* 870 */ 135, 110, 111, 112, 113, 114, 135, 135, 103, 135, + /* 880 */ 105, 135, 103, 134, 135, 110, 111, 112, 113, 114, + /* 890 */ 135, 112, 113, 114, 103, 134, 105, 103, 119, 120, + /* 900 */ 135, 110, 111, 112, 113, 114, 112, 113, 114, 134, + /* 910 */ 103, 135, 105, 134, 135, 135, 135, 110, 111, 112, + /* 920 */ 113, 114, 135, 135, 103, 134, 105, 135, 134, 135, + /* 930 */ 135, 110, 111, 112, 113, 114, 103, 135, 105, 135, + /* 940 */ 135, 134, 135, 110, 111, 112, 113, 114, 135, 103, + /* 950 */ 135, 105, 135, 135, 135, 134, 110, 111, 112, 113, + /* 960 */ 114, 135, 135, 103, 135, 105, 135, 134, 135, 135, + /* 970 */ 110, 111, 112, 113, 114, 103, 135, 105, 135, 135, + /* 980 */ 134, 135, 110, 111, 112, 113, 114, 135, 135, 103, + /* 990 */ 135, 105, 103, 135, 134, 135, 110, 111, 112, 113, + /* 1000 */ 114, 112, 113, 114, 135, 116, 134, 135, 103, 135, + /* 1010 */ 135, 135, 123, 124, 135, 135, 103, 112, 113, 114, + /* 1020 */ 134, 135, 135, 134, 119, 112, 113, 114, 135, 116, + /* 1030 */ 135, 126, 103, 128, 135, 103, 135, 124, 135, 134, + /* 1040 */ 135, 112, 113, 114, 112, 113, 114, 134, 103, 135, + /* 1050 */ 135, 119, 4, 5, 6, 7, 135, 112, 113, 114, + /* 1060 */ 103, 116, 135, 134, 103, 17, 134, 103, 135, 112, + /* 1070 */ 113, 114, 135, 112, 113, 114, 112, 113, 114, 134, + /* 1080 */ 135, 4, 5, 6, 7, 135, 135, 135, 135, 135, + /* 1090 */ 103, 134, 135, 103, 17, 134, 103, 135, 134, 112, + /* 1100 */ 113, 114, 112, 113, 114, 112, 113, 114, 135, 103, + /* 1110 */ 4, 5, 6, 7, 135, 135, 135, 135, 112, 113, + /* 1120 */ 114, 134, 103, 17, 134, 135, 135, 134, 103, 135, + /* 1130 */ 135, 112, 113, 114, 135, 103, 135, 112, 113, 114, + /* 1140 */ 134, 103, 135, 135, 112, 113, 114, 103, 135, 135, + /* 1150 */ 112, 113, 114, 134, 135, 135, 112, 113, 114, 134, + /* 1160 */ 135, 135, 135, 135, 103, 135, 134, 135, 135, 135, + /* 1170 */ 103, 135, 134, 112, 113, 114, 135, 103, 134, 112, + /* 1180 */ 113, 114, 103, 135, 135, 135, 112, 113, 114, 135, + /* 1190 */ 135, 112, 113, 114, 103, 134, 135, 135, 135, 135, + /* 1200 */ 103, 134, 135, 112, 113, 114, 103, 135, 134, 112, + /* 1210 */ 113, 114, 135, 134, 135, 112, 113, 114, 135, 135, + /* 1220 */ 135, 135, 135, 103, 135, 134, 135, 135, 135, 135, + /* 1230 */ 135, 134, 112, 113, 114, 103, 135, 134, 135, 103, + /* 1240 */ 135, 135, 103, 135, 112, 113, 114, 135, 112, 113, + /* 1250 */ 114, 112, 113, 114, 134, 4, 5, 6, 7, 4, + /* 1260 */ 5, 6, 7, 135, 135, 135, 134, 135, 135, 135, + /* 1270 */ 134, 135, 135, 134, 135, 135, 25, 135, 135, 135, + /* 1280 */ 25, 4, 5, 6, 7, 135, 135, 135, 135, 135, + /* 1290 */ 135, 135, 135, 135, 135, 135, 135, 135, 135, 135, + /* 1300 */ 135, 135, 25, 135, 135, 135, 135, 135, 135, 135, + /* 1310 */ 135, 135, 135, 135, 135, 135, 135, 135, 135, 135, + /* 1320 */ 135, 135, 135, 135, 135, 135, 135, 135, 135, 135, + /* 1330 */ 135, 135, 135, 135, 135, 135, 135, 135, 135, 135, + /* 1340 */ 135, 135, 135, 135, 135, 135, 135, 135, 135, 135, + /* 1350 */ 135, 135, 135, 135, 135, 135, 135, 135, 135, 135, + /* 1360 */ 135, 135, 135, 135, 135, 135, 135, 135, 135, 135, + /* 1370 */ 135, 135, 135, 135, 135, 135, 135, 135, 135, 135, + /* 1380 */ 135, 99, 99, 99, 99, 99, 99, 99, 99, 99, + /* 1390 */ 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, + /* 1400 */ 99, 99, +}; +#define YY_SHIFT_COUNT (163) +#define YY_SHIFT_MIN (0) +#define YY_SHIFT_MAX (1277) +static const unsigned short int yy_shift_ofst[] = { + /* 0 */ 143, 127, 221, 244, 244, 244, 244, 244, 244, 244, + /* 10 */ 244, 244, 244, 244, 244, 244, 244, 244, 244, 244, + /* 20 */ 244, 244, 244, 244, 244, 244, 244, 276, 510, 557, + /* 30 */ 276, 143, 347, 347, 0, 64, 143, 573, 557, 573, + /* 40 */ 400, 400, 400, 442, 539, 557, 557, 557, 557, 557, + /* 50 */ 557, 604, 557, 557, 667, 557, 557, 557, 557, 557, + /* 60 */ 557, 557, 557, 557, 557, 218, 60, 60, 60, 60, + /* 70 */ 60, 145, 315, 393, 471, 292, 292, 170, 71, 1303, + /* 80 */ 1303, 1303, 1303, 114, 114, 338, 402, 129, 444, 367, + /* 90 */ 683, 589, 1251, 669, 1255, 1048, 1277, 1077, 1106, 25, + /* 100 */ 25, 25, 184, 25, 25, 25, 168, 25, 429, 83, + /* 110 */ 92, 105, 70, 133, 138, 182, 182, 234, 257, 137, + /* 120 */ 149, 289, 141, 155, 151, 146, 156, 147, 174, 176, + /* 130 */ 196, 203, 204, 179, 237, 249, 213, 261, 211, 214, + /* 140 */ 215, 222, 290, 300, 307, 278, 323, 330, 336, 246, + /* 150 */ 274, 329, 246, 343, 345, 346, 348, 351, 352, 353, + /* 160 */ 372, 297, 384, 377, +}; +#define YY_REDUCE_COUNT (82) +#define YY_REDUCE_MIN (-129) +#define YY_REDUCE_MAX (1139) +static const short yy_reduce_ofst[] = { + /* 0 */ 363, -96, -32, 93, 152, 394, 512, 599, 616, 631, + /* 10 */ 664, 680, 696, 710, 722, 735, 749, 761, 775, 791, + /* 20 */ 807, 821, 833, 846, 860, 872, 886, 889, 668, 905, + /* 30 */ 913, 464, 603, 779, -61, -61, 535, 650, 932, 945, + /* 40 */ 794, 929, 957, 961, 964, 987, 990, 993, 1006, 1019, + /* 50 */ 1025, 1032, 1038, 1044, 1061, 1067, 1074, 1079, 1091, 1097, + /* 60 */ 1103, 1120, 1132, 1136, 1139, -81, -111, -101, -47, -37, + /* 70 */ -23, -22, -129, -129, -129, -97, -86, -58, -100, -15, + /* 80 */ 30, 34, 24, +}; +static const YYACTIONTYPE yy_default[] = { + /* 0 */ 449, 443, 443, 443, 443, 443, 443, 443, 443, 443, + /* 10 */ 443, 443, 443, 443, 443, 443, 443, 443, 443, 443, + /* 20 */ 443, 443, 443, 443, 443, 443, 443, 443, 473, 576, + /* 30 */ 443, 449, 580, 485, 581, 581, 449, 443, 443, 443, + /* 40 */ 443, 443, 443, 443, 443, 443, 443, 443, 477, 443, + /* 50 */ 443, 443, 443, 443, 443, 443, 443, 443, 443, 443, + /* 60 */ 443, 443, 443, 443, 443, 443, 443, 443, 443, 443, + /* 70 */ 443, 443, 443, 443, 443, 443, 443, 443, 455, 470, + /* 80 */ 508, 508, 576, 468, 493, 443, 443, 443, 471, 443, + /* 90 */ 443, 443, 443, 443, 443, 443, 443, 443, 443, 488, + /* 100 */ 486, 476, 459, 512, 511, 510, 443, 566, 443, 443, + /* 110 */ 443, 443, 443, 588, 443, 545, 544, 540, 443, 532, + /* 120 */ 529, 443, 443, 443, 443, 443, 443, 491, 443, 443, + /* 130 */ 443, 443, 443, 443, 443, 443, 443, 443, 443, 443, + /* 140 */ 443, 443, 443, 443, 443, 443, 443, 443, 443, 592, + /* 150 */ 443, 443, 443, 443, 443, 443, 443, 443, 443, 443, + /* 160 */ 443, 601, 443, 443, +}; +/********** End of lemon-generated parsing tables *****************************/ + +/* The next table maps tokens (terminal symbols) into fallback tokens. +** If a construct like the following: +** +** %fallback ID X Y Z. +** +** appears in the grammar, then ID becomes a fallback token for X, Y, +** and Z. Whenever one of the tokens X, Y, or Z is input to the parser +** but it does not parse, the type of the token is changed to ID and +** the parse is retried before an error is thrown. +** +** This feature can be used, for example, to cause some keywords in a language +** to revert to identifiers if they keyword does not apply in the context where +** it appears. +*/ +#ifdef YYFALLBACK +static const YYCODETYPE yyFallback[] = { + 0, /* $ => nothing */ + 0, /* ID => nothing */ + 1, /* EDGEPT => ID */ + 0, /* OF => nothing */ + 0, /* PLUS => nothing */ + 0, /* MINUS => nothing */ + 0, /* STAR => nothing */ + 0, /* SLASH => nothing */ + 0, /* PERCENT => nothing */ + 0, /* UMINUS => nothing */ + 0, /* EOL => nothing */ + 0, /* ASSIGN => nothing */ + 0, /* PLACENAME => nothing */ + 0, /* COLON => nothing */ + 0, /* ASSERT => nothing */ + 0, /* LP => nothing */ + 0, /* EQ => nothing */ + 0, /* RP => nothing */ + 0, /* DEFINE => nothing */ + 0, /* CODEBLOCK => nothing */ + 0, /* FILL => nothing */ + 0, /* COLOR => nothing */ + 0, /* THICKNESS => nothing */ + 0, /* PRINT => nothing */ + 0, /* STRING => nothing */ + 0, /* COMMA => nothing */ + 0, /* CLASSNAME => nothing */ + 0, /* LB => nothing */ + 0, /* RB => nothing */ + 0, /* UP => nothing */ + 0, /* DOWN => nothing */ + 0, /* LEFT => nothing */ + 0, /* RIGHT => nothing */ + 0, /* CLOSE => nothing */ + 0, /* CHOP => nothing */ + 0, /* FROM => nothing */ + 0, /* TO => nothing */ + 0, /* THEN => nothing */ + 0, /* HEADING => nothing */ + 0, /* GO => nothing */ + 0, /* AT => nothing */ + 0, /* WITH => nothing */ + 0, /* SAME => nothing */ + 0, /* AS => nothing */ + 0, /* FIT => nothing */ + 0, /* BEHIND => nothing */ + 0, /* UNTIL => nothing */ + 0, /* EVEN => nothing */ + 0, /* DOT_E => nothing */ + 0, /* HEIGHT => nothing */ + 0, /* WIDTH => nothing */ + 0, /* RADIUS => nothing */ + 0, /* DIAMETER => nothing */ + 0, /* DOTTED => nothing */ + 0, /* DASHED => nothing */ + 0, /* CW => nothing */ + 0, /* CCW => nothing */ + 0, /* LARROW => nothing */ + 0, /* RARROW => nothing */ + 0, /* LRARROW => nothing */ + 0, /* INVIS => nothing */ + 0, /* THICK => nothing */ + 0, /* THIN => nothing */ + 0, /* SOLID => nothing */ + 0, /* CENTER => nothing */ + 0, /* LJUST => nothing */ + 0, /* RJUST => nothing */ + 0, /* ABOVE => nothing */ + 0, /* BELOW => nothing */ + 0, /* ITALIC => nothing */ + 0, /* BOLD => nothing */ + 0, /* ALIGNED => nothing */ + 0, /* BIG => nothing */ + 0, /* SMALL => nothing */ + 0, /* AND => nothing */ + 0, /* LT => nothing */ + 0, /* GT => nothing */ + 0, /* ON => nothing */ + 0, /* WAY => nothing */ + 0, /* BETWEEN => nothing */ + 0, /* THE => nothing */ + 0, /* NTH => nothing */ + 0, /* VERTEX => nothing */ + 0, /* TOP => nothing */ + 0, /* BOTTOM => nothing */ + 0, /* START => nothing */ + 0, /* END => nothing */ + 0, /* IN => nothing */ + 0, /* THIS => nothing */ + 0, /* DOT_U => nothing */ + 0, /* LAST => nothing */ + 0, /* NUMBER => nothing */ + 0, /* FUNC1 => nothing */ + 0, /* FUNC2 => nothing */ + 0, /* DIST => nothing */ + 0, /* DOT_XY => nothing */ + 0, /* X => nothing */ + 0, /* Y => nothing */ + 0, /* DOT_L => nothing */ +}; +#endif /* YYFALLBACK */ + +/* The following structure represents a single element of the +** parser's stack. Information stored includes: +** +** + The state number for the parser at this level of the stack. +** +** + The value of the token stored at this level of the stack. +** (In other words, the "major" token.) +** +** + The semantic value stored at this level of the stack. This is +** the information used by the action routines in the grammar. +** It is sometimes called the "minor" token. +** +** After the "shift" half of a SHIFTREDUCE action, the stateno field +** actually contains the reduce action for the second half of the +** SHIFTREDUCE. +*/ +struct yyStackEntry { + YYACTIONTYPE stateno; /* The state-number, or reduce action in SHIFTREDUCE */ + YYCODETYPE major; /* The major token value. This is the code + ** number for the token at this stack level */ + YYMINORTYPE minor; /* The user-supplied minor token value. This + ** is the value of the token */ +}; +typedef struct yyStackEntry yyStackEntry; + +/* The state of the parser is completely contained in an instance of +** the following structure */ +struct yyParser { + yyStackEntry *yytos; /* Pointer to top element of the stack */ +#ifdef YYTRACKMAXSTACKDEPTH + int yyhwm; /* High-water mark of the stack */ +#endif +#ifndef YYNOERRORRECOVERY + int yyerrcnt; /* Shifts left before out of the error */ +#endif + pik_parserARG_SDECL /* A place to hold %extra_argument */ + pik_parserCTX_SDECL /* A place to hold %extra_context */ +#if YYSTACKDEPTH<=0 + int yystksz; /* Current side of the stack */ + yyStackEntry *yystack; /* The parser's stack */ + yyStackEntry yystk0; /* First stack entry */ +#else + yyStackEntry yystack[YYSTACKDEPTH]; /* The parser's stack */ + yyStackEntry *yystackEnd; /* Last entry in the stack */ +#endif +}; +typedef struct yyParser yyParser; + +#ifndef NDEBUG +#include +#include +static FILE *yyTraceFILE = 0; +static char *yyTracePrompt = 0; +#endif /* NDEBUG */ + +#ifndef NDEBUG +/* +** Turn parser tracing on by giving a stream to which to write the trace +** and a prompt to preface each trace message. Tracing is turned off +** by making either argument NULL +** +** Inputs: +**
    +**
  • A FILE* to which trace output should be written. +** If NULL, then tracing is turned off. +**
  • A prefix string written at the beginning of every +** line of trace output. If NULL, then tracing is +** turned off. +**
+** +** Outputs: +** None. +*/ +void pik_parserTrace(FILE *TraceFILE, char *zTracePrompt){ + yyTraceFILE = TraceFILE; + yyTracePrompt = zTracePrompt; + if( yyTraceFILE==0 ) yyTracePrompt = 0; + else if( yyTracePrompt==0 ) yyTraceFILE = 0; +} +#endif /* NDEBUG */ + +#if defined(YYCOVERAGE) || !defined(NDEBUG) +/* For tracing shifts, the names of all terminals and nonterminals +** are required. The following table supplies these names */ +static const char *const yyTokenName[] = { + /* 0 */ "$", + /* 1 */ "ID", + /* 2 */ "EDGEPT", + /* 3 */ "OF", + /* 4 */ "PLUS", + /* 5 */ "MINUS", + /* 6 */ "STAR", + /* 7 */ "SLASH", + /* 8 */ "PERCENT", + /* 9 */ "UMINUS", + /* 10 */ "EOL", + /* 11 */ "ASSIGN", + /* 12 */ "PLACENAME", + /* 13 */ "COLON", + /* 14 */ "ASSERT", + /* 15 */ "LP", + /* 16 */ "EQ", + /* 17 */ "RP", + /* 18 */ "DEFINE", + /* 19 */ "CODEBLOCK", + /* 20 */ "FILL", + /* 21 */ "COLOR", + /* 22 */ "THICKNESS", + /* 23 */ "PRINT", + /* 24 */ "STRING", + /* 25 */ "COMMA", + /* 26 */ "CLASSNAME", + /* 27 */ "LB", + /* 28 */ "RB", + /* 29 */ "UP", + /* 30 */ "DOWN", + /* 31 */ "LEFT", + /* 32 */ "RIGHT", + /* 33 */ "CLOSE", + /* 34 */ "CHOP", + /* 35 */ "FROM", + /* 36 */ "TO", + /* 37 */ "THEN", + /* 38 */ "HEADING", + /* 39 */ "GO", + /* 40 */ "AT", + /* 41 */ "WITH", + /* 42 */ "SAME", + /* 43 */ "AS", + /* 44 */ "FIT", + /* 45 */ "BEHIND", + /* 46 */ "UNTIL", + /* 47 */ "EVEN", + /* 48 */ "DOT_E", + /* 49 */ "HEIGHT", + /* 50 */ "WIDTH", + /* 51 */ "RADIUS", + /* 52 */ "DIAMETER", + /* 53 */ "DOTTED", + /* 54 */ "DASHED", + /* 55 */ "CW", + /* 56 */ "CCW", + /* 57 */ "LARROW", + /* 58 */ "RARROW", + /* 59 */ "LRARROW", + /* 60 */ "INVIS", + /* 61 */ "THICK", + /* 62 */ "THIN", + /* 63 */ "SOLID", + /* 64 */ "CENTER", + /* 65 */ "LJUST", + /* 66 */ "RJUST", + /* 67 */ "ABOVE", + /* 68 */ "BELOW", + /* 69 */ "ITALIC", + /* 70 */ "BOLD", + /* 71 */ "ALIGNED", + /* 72 */ "BIG", + /* 73 */ "SMALL", + /* 74 */ "AND", + /* 75 */ "LT", + /* 76 */ "GT", + /* 77 */ "ON", + /* 78 */ "WAY", + /* 79 */ "BETWEEN", + /* 80 */ "THE", + /* 81 */ "NTH", + /* 82 */ "VERTEX", + /* 83 */ "TOP", + /* 84 */ "BOTTOM", + /* 85 */ "START", + /* 86 */ "END", + /* 87 */ "IN", + /* 88 */ "THIS", + /* 89 */ "DOT_U", + /* 90 */ "LAST", + /* 91 */ "NUMBER", + /* 92 */ "FUNC1", + /* 93 */ "FUNC2", + /* 94 */ "DIST", + /* 95 */ "DOT_XY", + /* 96 */ "X", + /* 97 */ "Y", + /* 98 */ "DOT_L", + /* 99 */ "statement_list", + /* 100 */ "statement", + /* 101 */ "unnamed_statement", + /* 102 */ "basetype", + /* 103 */ "expr", + /* 104 */ "numproperty", + /* 105 */ "edge", + /* 106 */ "direction", + /* 107 */ "dashproperty", + /* 108 */ "colorproperty", + /* 109 */ "locproperty", + /* 110 */ "position", + /* 111 */ "place", + /* 112 */ "object", + /* 113 */ "objectname", + /* 114 */ "nth", + /* 115 */ "textposition", + /* 116 */ "rvalue", + /* 117 */ "lvalue", + /* 118 */ "even", + /* 119 */ "relexpr", + /* 120 */ "optrelexpr", + /* 121 */ "document", + /* 122 */ "print", + /* 123 */ "prlist", + /* 124 */ "pritem", + /* 125 */ "prsep", + /* 126 */ "attribute_list", + /* 127 */ "savelist", + /* 128 */ "alist", + /* 129 */ "attribute", + /* 130 */ "go", + /* 131 */ "boolproperty", + /* 132 */ "withclause", + /* 133 */ "between", + /* 134 */ "place2", +}; +#endif /* defined(YYCOVERAGE) || !defined(NDEBUG) */ + +#ifndef NDEBUG +/* For tracing reduce actions, the names of all rules are required. +*/ +static const char *const yyRuleName[] = { + /* 0 */ "document ::= statement_list", + /* 1 */ "statement_list ::= statement", + /* 2 */ "statement_list ::= statement_list EOL statement", + /* 3 */ "statement ::=", + /* 4 */ "statement ::= direction", + /* 5 */ "statement ::= lvalue ASSIGN rvalue", + /* 6 */ "statement ::= PLACENAME COLON unnamed_statement", + /* 7 */ "statement ::= PLACENAME COLON position", + /* 8 */ "statement ::= unnamed_statement", + /* 9 */ "statement ::= print prlist", + /* 10 */ "statement ::= ASSERT LP expr EQ expr RP", + /* 11 */ "statement ::= ASSERT LP position EQ position RP", + /* 12 */ "statement ::= DEFINE ID CODEBLOCK", + /* 13 */ "rvalue ::= PLACENAME", + /* 14 */ "pritem ::= FILL", + /* 15 */ "pritem ::= COLOR", + /* 16 */ "pritem ::= THICKNESS", + /* 17 */ "pritem ::= rvalue", + /* 18 */ "pritem ::= STRING", + /* 19 */ "prsep ::= COMMA", + /* 20 */ "unnamed_statement ::= basetype attribute_list", + /* 21 */ "basetype ::= CLASSNAME", + /* 22 */ "basetype ::= STRING textposition", + /* 23 */ "basetype ::= LB savelist statement_list RB", + /* 24 */ "savelist ::=", + /* 25 */ "relexpr ::= expr", + /* 26 */ "relexpr ::= expr PERCENT", + /* 27 */ "optrelexpr ::=", + /* 28 */ "attribute_list ::= relexpr alist", + /* 29 */ "attribute ::= numproperty relexpr", + /* 30 */ "attribute ::= dashproperty expr", + /* 31 */ "attribute ::= dashproperty", + /* 32 */ "attribute ::= colorproperty rvalue", + /* 33 */ "attribute ::= go direction optrelexpr", + /* 34 */ "attribute ::= go direction even position", + /* 35 */ "attribute ::= CLOSE", + /* 36 */ "attribute ::= CHOP", + /* 37 */ "attribute ::= FROM position", + /* 38 */ "attribute ::= TO position", + /* 39 */ "attribute ::= THEN", + /* 40 */ "attribute ::= THEN optrelexpr HEADING expr", + /* 41 */ "attribute ::= THEN optrelexpr EDGEPT", + /* 42 */ "attribute ::= GO optrelexpr HEADING expr", + /* 43 */ "attribute ::= GO optrelexpr EDGEPT", + /* 44 */ "attribute ::= AT position", + /* 45 */ "attribute ::= SAME", + /* 46 */ "attribute ::= SAME AS object", + /* 47 */ "attribute ::= STRING textposition", + /* 48 */ "attribute ::= FIT", + /* 49 */ "attribute ::= BEHIND object", + /* 50 */ "withclause ::= DOT_E edge AT position", + /* 51 */ "withclause ::= edge AT position", + /* 52 */ "numproperty ::= HEIGHT|WIDTH|RADIUS|DIAMETER|THICKNESS", + /* 53 */ "boolproperty ::= CW", + /* 54 */ "boolproperty ::= CCW", + /* 55 */ "boolproperty ::= LARROW", + /* 56 */ "boolproperty ::= RARROW", + /* 57 */ "boolproperty ::= LRARROW", + /* 58 */ "boolproperty ::= INVIS", + /* 59 */ "boolproperty ::= THICK", + /* 60 */ "boolproperty ::= THIN", + /* 61 */ "boolproperty ::= SOLID", + /* 62 */ "textposition ::=", + /* 63 */ "textposition ::= textposition CENTER|LJUST|RJUST|ABOVE|BELOW|ITALIC|BOLD|ALIGNED|BIG|SMALL", + /* 64 */ "position ::= expr COMMA expr", + /* 65 */ "position ::= place PLUS expr COMMA expr", + /* 66 */ "position ::= place MINUS expr COMMA expr", + /* 67 */ "position ::= place PLUS LP expr COMMA expr RP", + /* 68 */ "position ::= place MINUS LP expr COMMA expr RP", + /* 69 */ "position ::= LP position COMMA position RP", + /* 70 */ "position ::= LP position RP", + /* 71 */ "position ::= expr between position AND position", + /* 72 */ "position ::= expr LT position COMMA position GT", + /* 73 */ "position ::= expr ABOVE position", + /* 74 */ "position ::= expr BELOW position", + /* 75 */ "position ::= expr LEFT OF position", + /* 76 */ "position ::= expr RIGHT OF position", + /* 77 */ "position ::= expr ON HEADING EDGEPT OF position", + /* 78 */ "position ::= expr HEADING EDGEPT OF position", + /* 79 */ "position ::= expr EDGEPT OF position", + /* 80 */ "position ::= expr ON HEADING expr FROM position", + /* 81 */ "position ::= expr HEADING expr FROM position", + /* 82 */ "place ::= edge OF object", + /* 83 */ "place2 ::= object", + /* 84 */ "place2 ::= object DOT_E edge", + /* 85 */ "place2 ::= NTH VERTEX OF object", + /* 86 */ "object ::= nth", + /* 87 */ "object ::= nth OF|IN object", + /* 88 */ "objectname ::= THIS", + /* 89 */ "objectname ::= PLACENAME", + /* 90 */ "objectname ::= objectname DOT_U PLACENAME", + /* 91 */ "nth ::= NTH CLASSNAME", + /* 92 */ "nth ::= NTH LAST CLASSNAME", + /* 93 */ "nth ::= LAST CLASSNAME", + /* 94 */ "nth ::= LAST", + /* 95 */ "nth ::= NTH LB RB", + /* 96 */ "nth ::= NTH LAST LB RB", + /* 97 */ "nth ::= LAST LB RB", + /* 98 */ "expr ::= expr PLUS expr", + /* 99 */ "expr ::= expr MINUS expr", + /* 100 */ "expr ::= expr STAR expr", + /* 101 */ "expr ::= expr SLASH expr", + /* 102 */ "expr ::= MINUS expr", + /* 103 */ "expr ::= PLUS expr", + /* 104 */ "expr ::= LP expr RP", + /* 105 */ "expr ::= LP FILL|COLOR|THICKNESS RP", + /* 106 */ "expr ::= NUMBER", + /* 107 */ "expr ::= ID", + /* 108 */ "expr ::= FUNC1 LP expr RP", + /* 109 */ "expr ::= FUNC2 LP expr COMMA expr RP", + /* 110 */ "expr ::= DIST LP position COMMA position RP", + /* 111 */ "expr ::= place2 DOT_XY X", + /* 112 */ "expr ::= place2 DOT_XY Y", + /* 113 */ "expr ::= object DOT_L numproperty", + /* 114 */ "expr ::= object DOT_L dashproperty", + /* 115 */ "expr ::= object DOT_L colorproperty", + /* 116 */ "lvalue ::= ID", + /* 117 */ "lvalue ::= FILL", + /* 118 */ "lvalue ::= COLOR", + /* 119 */ "lvalue ::= THICKNESS", + /* 120 */ "rvalue ::= expr", + /* 121 */ "print ::= PRINT", + /* 122 */ "prlist ::= pritem", + /* 123 */ "prlist ::= prlist prsep pritem", + /* 124 */ "direction ::= UP", + /* 125 */ "direction ::= DOWN", + /* 126 */ "direction ::= LEFT", + /* 127 */ "direction ::= RIGHT", + /* 128 */ "optrelexpr ::= relexpr", + /* 129 */ "attribute_list ::= alist", + /* 130 */ "alist ::=", + /* 131 */ "alist ::= alist attribute", + /* 132 */ "attribute ::= boolproperty", + /* 133 */ "attribute ::= WITH withclause", + /* 134 */ "go ::= GO", + /* 135 */ "go ::=", + /* 136 */ "even ::= UNTIL EVEN WITH", + /* 137 */ "even ::= EVEN WITH", + /* 138 */ "dashproperty ::= DOTTED", + /* 139 */ "dashproperty ::= DASHED", + /* 140 */ "colorproperty ::= FILL", + /* 141 */ "colorproperty ::= COLOR", + /* 142 */ "position ::= place", + /* 143 */ "between ::= WAY BETWEEN", + /* 144 */ "between ::= BETWEEN", + /* 145 */ "between ::= OF THE WAY BETWEEN", + /* 146 */ "place ::= place2", + /* 147 */ "edge ::= CENTER", + /* 148 */ "edge ::= EDGEPT", + /* 149 */ "edge ::= TOP", + /* 150 */ "edge ::= BOTTOM", + /* 151 */ "edge ::= START", + /* 152 */ "edge ::= END", + /* 153 */ "edge ::= RIGHT", + /* 154 */ "edge ::= LEFT", + /* 155 */ "object ::= objectname", +}; +#endif /* NDEBUG */ + + +#if YYSTACKDEPTH<=0 +/* +** Try to increase the size of the parser stack. Return the number +** of errors. Return 0 on success. +*/ +static int yyGrowStack(yyParser *p){ + int newSize; + int idx; + yyStackEntry *pNew; + + newSize = p->yystksz*2 + 100; + idx = p->yytos ? (int)(p->yytos - p->yystack) : 0; + if( p->yystack==&p->yystk0 ){ + pNew = malloc(newSize*sizeof(pNew[0])); + if( pNew ) pNew[0] = p->yystk0; + }else{ + pNew = realloc(p->yystack, newSize*sizeof(pNew[0])); + } + if( pNew ){ + p->yystack = pNew; + p->yytos = &p->yystack[idx]; +#ifndef NDEBUG + if( yyTraceFILE ){ + fprintf(yyTraceFILE,"%sStack grows from %d to %d entries.\n", + yyTracePrompt, p->yystksz, newSize); + } +#endif + p->yystksz = newSize; + } + return pNew==0; +} +#endif + +/* Datatype of the argument to the memory allocated passed as the +** second argument to pik_parserAlloc() below. This can be changed by +** putting an appropriate #define in the %include section of the input +** grammar. +*/ +#ifndef YYMALLOCARGTYPE +# define YYMALLOCARGTYPE size_t +#endif + +/* Initialize a new parser that has already been allocated. +*/ +void pik_parserInit(void *yypRawParser pik_parserCTX_PDECL){ + yyParser *yypParser = (yyParser*)yypRawParser; + pik_parserCTX_STORE +#ifdef YYTRACKMAXSTACKDEPTH + yypParser->yyhwm = 0; +#endif +#if YYSTACKDEPTH<=0 + yypParser->yytos = NULL; + yypParser->yystack = NULL; + yypParser->yystksz = 0; + if( yyGrowStack(yypParser) ){ + yypParser->yystack = &yypParser->yystk0; + yypParser->yystksz = 1; + } +#endif +#ifndef YYNOERRORRECOVERY + yypParser->yyerrcnt = -1; +#endif + yypParser->yytos = yypParser->yystack; + yypParser->yystack[0].stateno = 0; + yypParser->yystack[0].major = 0; +#if YYSTACKDEPTH>0 + yypParser->yystackEnd = &yypParser->yystack[YYSTACKDEPTH-1]; +#endif +} + +#ifndef pik_parser_ENGINEALWAYSONSTACK +/* +** This function allocates a new parser. +** The only argument is a pointer to a function which works like +** malloc. +** +** Inputs: +** A pointer to the function used to allocate memory. +** +** Outputs: +** A pointer to a parser. This pointer is used in subsequent calls +** to pik_parser and pik_parserFree. +*/ +void *pik_parserAlloc(void *(*mallocProc)(YYMALLOCARGTYPE) pik_parserCTX_PDECL){ + yyParser *yypParser; + yypParser = (yyParser*)(*mallocProc)( (YYMALLOCARGTYPE)sizeof(yyParser) ); + if( yypParser ){ + pik_parserCTX_STORE + pik_parserInit(yypParser pik_parserCTX_PARAM); + } + return (void*)yypParser; +} +#endif /* pik_parser_ENGINEALWAYSONSTACK */ + + +/* The following function deletes the "minor type" or semantic value +** associated with a symbol. The symbol can be either a terminal +** or nonterminal. "yymajor" is the symbol code, and "yypminor" is +** a pointer to the value to be deleted. The code used to do the +** deletions is derived from the %destructor and/or %token_destructor +** directives of the input grammar. +*/ +static void yy_destructor( + yyParser *yypParser, /* The parser */ + YYCODETYPE yymajor, /* Type code for object to destroy */ + YYMINORTYPE *yypminor /* The object to be destroyed */ +){ + pik_parserARG_FETCH + pik_parserCTX_FETCH + switch( yymajor ){ + /* Here is inserted the actions which take place when a + ** terminal or non-terminal is destroyed. This can happen + ** when the symbol is popped from the stack during a + ** reduce or during error processing or when a parser is + ** being destroyed before it is finished parsing. + ** + ** Note: during a reduce, the only symbols destroyed are those + ** which appear on the RHS of the rule, but which are *not* used + ** inside the C code. + */ +/********* Begin destructor definitions ***************************************/ + case 99: /* statement_list */ +{ +#line 499 "pikchr.y" +pik_elist_free(p,(yypminor->yy227)); +#line 1740 "pikchr.c" +} + break; + case 100: /* statement */ + case 101: /* unnamed_statement */ + case 102: /* basetype */ +{ +#line 501 "pikchr.y" +pik_elem_free(p,(yypminor->yy36)); +#line 1749 "pikchr.c" +} + break; +/********* End destructor definitions *****************************************/ + default: break; /* If no destructor action specified: do nothing */ + } +} + +/* +** Pop the parser's stack once. +** +** If there is a destructor routine associated with the token which +** is popped from the stack, then call it. +*/ +static void yy_pop_parser_stack(yyParser *pParser){ + yyStackEntry *yytos; + assert( pParser->yytos!=0 ); + assert( pParser->yytos > pParser->yystack ); + yytos = pParser->yytos--; +#ifndef NDEBUG + if( yyTraceFILE ){ + fprintf(yyTraceFILE,"%sPopping %s\n", + yyTracePrompt, + yyTokenName[yytos->major]); + } +#endif + yy_destructor(pParser, yytos->major, &yytos->minor); +} + +/* +** Clear all secondary memory allocations from the parser +*/ +void pik_parserFinalize(void *p){ + yyParser *pParser = (yyParser*)p; + while( pParser->yytos>pParser->yystack ) yy_pop_parser_stack(pParser); +#if YYSTACKDEPTH<=0 + if( pParser->yystack!=&pParser->yystk0 ) free(pParser->yystack); +#endif +} + +#ifndef pik_parser_ENGINEALWAYSONSTACK +/* +** Deallocate and destroy a parser. Destructors are called for +** all stack elements before shutting the parser down. +** +** If the YYPARSEFREENEVERNULL macro exists (for example because it +** is defined in a %include section of the input grammar) then it is +** assumed that the input pointer is never NULL. +*/ +void pik_parserFree( + void *p, /* The parser to be deleted */ + void (*freeProc)(void*) /* Function used to reclaim memory */ +){ +#ifndef YYPARSEFREENEVERNULL + if( p==0 ) return; +#endif + pik_parserFinalize(p); + (*freeProc)(p); +} +#endif /* pik_parser_ENGINEALWAYSONSTACK */ + +/* +** Return the peak depth of the stack for a parser. +*/ +#ifdef YYTRACKMAXSTACKDEPTH +int pik_parserStackPeak(void *p){ + yyParser *pParser = (yyParser*)p; + return pParser->yyhwm; +} +#endif + +/* This array of booleans keeps track of the parser statement +** coverage. The element yycoverage[X][Y] is set when the parser +** is in state X and has a lookahead token Y. In a well-tested +** systems, every element of this matrix should end up being set. +*/ +#if defined(YYCOVERAGE) +static unsigned char yycoverage[YYNSTATE][YYNTOKEN]; +#endif + +/* +** Write into out a description of every state/lookahead combination that +** +** (1) has not been used by the parser, and +** (2) is not a syntax error. +** +** Return the number of missed state/lookahead combinations. +*/ +#if defined(YYCOVERAGE) +int pik_parserCoverage(FILE *out){ + int stateno, iLookAhead, i; + int nMissed = 0; + for(stateno=0; statenoYY_MAX_SHIFT ) return stateno; + assert( stateno <= YY_SHIFT_COUNT ); +#if defined(YYCOVERAGE) + yycoverage[stateno][iLookAhead] = 1; +#endif + do{ + i = yy_shift_ofst[stateno]; + assert( i>=0 ); + assert( i<=YY_ACTTAB_COUNT ); + assert( i+YYNTOKEN<=(int)YY_NLOOKAHEAD ); + assert( iLookAhead!=YYNOCODE ); + assert( iLookAhead < YYNTOKEN ); + i += iLookAhead; + assert( i<(int)YY_NLOOKAHEAD ); + if( yy_lookahead[i]!=iLookAhead ){ +#ifdef YYFALLBACK + YYCODETYPE iFallback; /* Fallback token */ + assert( iLookAhead %s\n", + yyTracePrompt, yyTokenName[iLookAhead], yyTokenName[iFallback]); + } +#endif + assert( yyFallback[iFallback]==0 ); /* Fallback loop must terminate */ + iLookAhead = iFallback; + continue; + } +#endif +#ifdef YYWILDCARD + { + int j = i - iLookAhead + YYWILDCARD; + assert( j<(int)(sizeof(yy_lookahead)/sizeof(yy_lookahead[0])) ); + if( yy_lookahead[j]==YYWILDCARD && iLookAhead>0 ){ +#ifndef NDEBUG + if( yyTraceFILE ){ + fprintf(yyTraceFILE, "%sWILDCARD %s => %s\n", + yyTracePrompt, yyTokenName[iLookAhead], + yyTokenName[YYWILDCARD]); + } +#endif /* NDEBUG */ + return yy_action[j]; + } + } +#endif /* YYWILDCARD */ + return yy_default[stateno]; + }else{ + assert( i>=0 && i<(int)(sizeof(yy_action)/sizeof(yy_action[0])) ); + return yy_action[i]; + } + }while(1); +} + +/* +** Find the appropriate action for a parser given the non-terminal +** look-ahead token iLookAhead. +*/ +static YYACTIONTYPE yy_find_reduce_action( + YYACTIONTYPE stateno, /* Current state number */ + YYCODETYPE iLookAhead /* The look-ahead token */ +){ + int i; +#ifdef YYERRORSYMBOL + if( stateno>YY_REDUCE_COUNT ){ + return yy_default[stateno]; + } +#else + assert( stateno<=YY_REDUCE_COUNT ); +#endif + i = yy_reduce_ofst[stateno]; + assert( iLookAhead!=YYNOCODE ); + i += iLookAhead; +#ifdef YYERRORSYMBOL + if( i<0 || i>=YY_ACTTAB_COUNT || yy_lookahead[i]!=iLookAhead ){ + return yy_default[stateno]; + } +#else + assert( i>=0 && iyytos>yypParser->yystack ) yy_pop_parser_stack(yypParser); + /* Here code is inserted which will execute if the parser + ** stack every overflows */ +/******** Begin %stack_overflow code ******************************************/ +#line 533 "pikchr.y" + + pik_error(p, 0, "parser stack overflow"); +#line 1970 "pikchr.c" +/******** End %stack_overflow code ********************************************/ + pik_parserARG_STORE /* Suppress warning about unused %extra_argument var */ + pik_parserCTX_STORE +} + +/* +** Print tracing information for a SHIFT action +*/ +#ifndef NDEBUG +static void yyTraceShift(yyParser *yypParser, int yyNewState, const char *zTag){ + if( yyTraceFILE ){ + if( yyNewStateyytos->major], + yyNewState); + }else{ + fprintf(yyTraceFILE,"%s%s '%s', pending reduce %d\n", + yyTracePrompt, zTag, yyTokenName[yypParser->yytos->major], + yyNewState - YY_MIN_REDUCE); + } + } +} +#else +# define yyTraceShift(X,Y,Z) +#endif + +/* +** Perform a shift action. +*/ +static void yy_shift( + yyParser *yypParser, /* The parser to be shifted */ + YYACTIONTYPE yyNewState, /* The new state to shift in */ + YYCODETYPE yyMajor, /* The major token to shift in */ + pik_parserTOKENTYPE yyMinor /* The minor token to shift in */ +){ + yyStackEntry *yytos; + yypParser->yytos++; +#ifdef YYTRACKMAXSTACKDEPTH + if( (int)(yypParser->yytos - yypParser->yystack)>yypParser->yyhwm ){ + yypParser->yyhwm++; + assert( yypParser->yyhwm == (int)(yypParser->yytos - yypParser->yystack) ); + } +#endif +#if YYSTACKDEPTH>0 + if( yypParser->yytos>yypParser->yystackEnd ){ + yypParser->yytos--; + yyStackOverflow(yypParser); + return; + } +#else + if( yypParser->yytos>=&yypParser->yystack[yypParser->yystksz] ){ + if( yyGrowStack(yypParser) ){ + yypParser->yytos--; + yyStackOverflow(yypParser); + return; + } + } +#endif + if( yyNewState > YY_MAX_SHIFT ){ + yyNewState += YY_MIN_REDUCE - YY_MIN_SHIFTREDUCE; + } + yytos = yypParser->yytos; + yytos->stateno = yyNewState; + yytos->major = yyMajor; + yytos->minor.yy0 = yyMinor; + yyTraceShift(yypParser, yyNewState, "Shift"); +} + +/* For rule J, yyRuleInfoLhs[J] contains the symbol on the left-hand side +** of that rule */ +static const YYCODETYPE yyRuleInfoLhs[] = { + 121, /* (0) document ::= statement_list */ + 99, /* (1) statement_list ::= statement */ + 99, /* (2) statement_list ::= statement_list EOL statement */ + 100, /* (3) statement ::= */ + 100, /* (4) statement ::= direction */ + 100, /* (5) statement ::= lvalue ASSIGN rvalue */ + 100, /* (6) statement ::= PLACENAME COLON unnamed_statement */ + 100, /* (7) statement ::= PLACENAME COLON position */ + 100, /* (8) statement ::= unnamed_statement */ + 100, /* (9) statement ::= print prlist */ + 100, /* (10) statement ::= ASSERT LP expr EQ expr RP */ + 100, /* (11) statement ::= ASSERT LP position EQ position RP */ + 100, /* (12) statement ::= DEFINE ID CODEBLOCK */ + 116, /* (13) rvalue ::= PLACENAME */ + 124, /* (14) pritem ::= FILL */ + 124, /* (15) pritem ::= COLOR */ + 124, /* (16) pritem ::= THICKNESS */ + 124, /* (17) pritem ::= rvalue */ + 124, /* (18) pritem ::= STRING */ + 125, /* (19) prsep ::= COMMA */ + 101, /* (20) unnamed_statement ::= basetype attribute_list */ + 102, /* (21) basetype ::= CLASSNAME */ + 102, /* (22) basetype ::= STRING textposition */ + 102, /* (23) basetype ::= LB savelist statement_list RB */ + 127, /* (24) savelist ::= */ + 119, /* (25) relexpr ::= expr */ + 119, /* (26) relexpr ::= expr PERCENT */ + 120, /* (27) optrelexpr ::= */ + 126, /* (28) attribute_list ::= relexpr alist */ + 129, /* (29) attribute ::= numproperty relexpr */ + 129, /* (30) attribute ::= dashproperty expr */ + 129, /* (31) attribute ::= dashproperty */ + 129, /* (32) attribute ::= colorproperty rvalue */ + 129, /* (33) attribute ::= go direction optrelexpr */ + 129, /* (34) attribute ::= go direction even position */ + 129, /* (35) attribute ::= CLOSE */ + 129, /* (36) attribute ::= CHOP */ + 129, /* (37) attribute ::= FROM position */ + 129, /* (38) attribute ::= TO position */ + 129, /* (39) attribute ::= THEN */ + 129, /* (40) attribute ::= THEN optrelexpr HEADING expr */ + 129, /* (41) attribute ::= THEN optrelexpr EDGEPT */ + 129, /* (42) attribute ::= GO optrelexpr HEADING expr */ + 129, /* (43) attribute ::= GO optrelexpr EDGEPT */ + 129, /* (44) attribute ::= AT position */ + 129, /* (45) attribute ::= SAME */ + 129, /* (46) attribute ::= SAME AS object */ + 129, /* (47) attribute ::= STRING textposition */ + 129, /* (48) attribute ::= FIT */ + 129, /* (49) attribute ::= BEHIND object */ + 132, /* (50) withclause ::= DOT_E edge AT position */ + 132, /* (51) withclause ::= edge AT position */ + 104, /* (52) numproperty ::= HEIGHT|WIDTH|RADIUS|DIAMETER|THICKNESS */ + 131, /* (53) boolproperty ::= CW */ + 131, /* (54) boolproperty ::= CCW */ + 131, /* (55) boolproperty ::= LARROW */ + 131, /* (56) boolproperty ::= RARROW */ + 131, /* (57) boolproperty ::= LRARROW */ + 131, /* (58) boolproperty ::= INVIS */ + 131, /* (59) boolproperty ::= THICK */ + 131, /* (60) boolproperty ::= THIN */ + 131, /* (61) boolproperty ::= SOLID */ + 115, /* (62) textposition ::= */ + 115, /* (63) textposition ::= textposition CENTER|LJUST|RJUST|ABOVE|BELOW|ITALIC|BOLD|ALIGNED|BIG|SMALL */ + 110, /* (64) position ::= expr COMMA expr */ + 110, /* (65) position ::= place PLUS expr COMMA expr */ + 110, /* (66) position ::= place MINUS expr COMMA expr */ + 110, /* (67) position ::= place PLUS LP expr COMMA expr RP */ + 110, /* (68) position ::= place MINUS LP expr COMMA expr RP */ + 110, /* (69) position ::= LP position COMMA position RP */ + 110, /* (70) position ::= LP position RP */ + 110, /* (71) position ::= expr between position AND position */ + 110, /* (72) position ::= expr LT position COMMA position GT */ + 110, /* (73) position ::= expr ABOVE position */ + 110, /* (74) position ::= expr BELOW position */ + 110, /* (75) position ::= expr LEFT OF position */ + 110, /* (76) position ::= expr RIGHT OF position */ + 110, /* (77) position ::= expr ON HEADING EDGEPT OF position */ + 110, /* (78) position ::= expr HEADING EDGEPT OF position */ + 110, /* (79) position ::= expr EDGEPT OF position */ + 110, /* (80) position ::= expr ON HEADING expr FROM position */ + 110, /* (81) position ::= expr HEADING expr FROM position */ + 111, /* (82) place ::= edge OF object */ + 134, /* (83) place2 ::= object */ + 134, /* (84) place2 ::= object DOT_E edge */ + 134, /* (85) place2 ::= NTH VERTEX OF object */ + 112, /* (86) object ::= nth */ + 112, /* (87) object ::= nth OF|IN object */ + 113, /* (88) objectname ::= THIS */ + 113, /* (89) objectname ::= PLACENAME */ + 113, /* (90) objectname ::= objectname DOT_U PLACENAME */ + 114, /* (91) nth ::= NTH CLASSNAME */ + 114, /* (92) nth ::= NTH LAST CLASSNAME */ + 114, /* (93) nth ::= LAST CLASSNAME */ + 114, /* (94) nth ::= LAST */ + 114, /* (95) nth ::= NTH LB RB */ + 114, /* (96) nth ::= NTH LAST LB RB */ + 114, /* (97) nth ::= LAST LB RB */ + 103, /* (98) expr ::= expr PLUS expr */ + 103, /* (99) expr ::= expr MINUS expr */ + 103, /* (100) expr ::= expr STAR expr */ + 103, /* (101) expr ::= expr SLASH expr */ + 103, /* (102) expr ::= MINUS expr */ + 103, /* (103) expr ::= PLUS expr */ + 103, /* (104) expr ::= LP expr RP */ + 103, /* (105) expr ::= LP FILL|COLOR|THICKNESS RP */ + 103, /* (106) expr ::= NUMBER */ + 103, /* (107) expr ::= ID */ + 103, /* (108) expr ::= FUNC1 LP expr RP */ + 103, /* (109) expr ::= FUNC2 LP expr COMMA expr RP */ + 103, /* (110) expr ::= DIST LP position COMMA position RP */ + 103, /* (111) expr ::= place2 DOT_XY X */ + 103, /* (112) expr ::= place2 DOT_XY Y */ + 103, /* (113) expr ::= object DOT_L numproperty */ + 103, /* (114) expr ::= object DOT_L dashproperty */ + 103, /* (115) expr ::= object DOT_L colorproperty */ + 117, /* (116) lvalue ::= ID */ + 117, /* (117) lvalue ::= FILL */ + 117, /* (118) lvalue ::= COLOR */ + 117, /* (119) lvalue ::= THICKNESS */ + 116, /* (120) rvalue ::= expr */ + 122, /* (121) print ::= PRINT */ + 123, /* (122) prlist ::= pritem */ + 123, /* (123) prlist ::= prlist prsep pritem */ + 106, /* (124) direction ::= UP */ + 106, /* (125) direction ::= DOWN */ + 106, /* (126) direction ::= LEFT */ + 106, /* (127) direction ::= RIGHT */ + 120, /* (128) optrelexpr ::= relexpr */ + 126, /* (129) attribute_list ::= alist */ + 128, /* (130) alist ::= */ + 128, /* (131) alist ::= alist attribute */ + 129, /* (132) attribute ::= boolproperty */ + 129, /* (133) attribute ::= WITH withclause */ + 130, /* (134) go ::= GO */ + 130, /* (135) go ::= */ + 118, /* (136) even ::= UNTIL EVEN WITH */ + 118, /* (137) even ::= EVEN WITH */ + 107, /* (138) dashproperty ::= DOTTED */ + 107, /* (139) dashproperty ::= DASHED */ + 108, /* (140) colorproperty ::= FILL */ + 108, /* (141) colorproperty ::= COLOR */ + 110, /* (142) position ::= place */ + 133, /* (143) between ::= WAY BETWEEN */ + 133, /* (144) between ::= BETWEEN */ + 133, /* (145) between ::= OF THE WAY BETWEEN */ + 111, /* (146) place ::= place2 */ + 105, /* (147) edge ::= CENTER */ + 105, /* (148) edge ::= EDGEPT */ + 105, /* (149) edge ::= TOP */ + 105, /* (150) edge ::= BOTTOM */ + 105, /* (151) edge ::= START */ + 105, /* (152) edge ::= END */ + 105, /* (153) edge ::= RIGHT */ + 105, /* (154) edge ::= LEFT */ + 112, /* (155) object ::= objectname */ +}; + +/* For rule J, yyRuleInfoNRhs[J] contains the negative of the number +** of symbols on the right-hand side of that rule. */ +static const signed char yyRuleInfoNRhs[] = { + -1, /* (0) document ::= statement_list */ + -1, /* (1) statement_list ::= statement */ + -3, /* (2) statement_list ::= statement_list EOL statement */ + 0, /* (3) statement ::= */ + -1, /* (4) statement ::= direction */ + -3, /* (5) statement ::= lvalue ASSIGN rvalue */ + -3, /* (6) statement ::= PLACENAME COLON unnamed_statement */ + -3, /* (7) statement ::= PLACENAME COLON position */ + -1, /* (8) statement ::= unnamed_statement */ + -2, /* (9) statement ::= print prlist */ + -6, /* (10) statement ::= ASSERT LP expr EQ expr RP */ + -6, /* (11) statement ::= ASSERT LP position EQ position RP */ + -3, /* (12) statement ::= DEFINE ID CODEBLOCK */ + -1, /* (13) rvalue ::= PLACENAME */ + -1, /* (14) pritem ::= FILL */ + -1, /* (15) pritem ::= COLOR */ + -1, /* (16) pritem ::= THICKNESS */ + -1, /* (17) pritem ::= rvalue */ + -1, /* (18) pritem ::= STRING */ + -1, /* (19) prsep ::= COMMA */ + -2, /* (20) unnamed_statement ::= basetype attribute_list */ + -1, /* (21) basetype ::= CLASSNAME */ + -2, /* (22) basetype ::= STRING textposition */ + -4, /* (23) basetype ::= LB savelist statement_list RB */ + 0, /* (24) savelist ::= */ + -1, /* (25) relexpr ::= expr */ + -2, /* (26) relexpr ::= expr PERCENT */ + 0, /* (27) optrelexpr ::= */ + -2, /* (28) attribute_list ::= relexpr alist */ + -2, /* (29) attribute ::= numproperty relexpr */ + -2, /* (30) attribute ::= dashproperty expr */ + -1, /* (31) attribute ::= dashproperty */ + -2, /* (32) attribute ::= colorproperty rvalue */ + -3, /* (33) attribute ::= go direction optrelexpr */ + -4, /* (34) attribute ::= go direction even position */ + -1, /* (35) attribute ::= CLOSE */ + -1, /* (36) attribute ::= CHOP */ + -2, /* (37) attribute ::= FROM position */ + -2, /* (38) attribute ::= TO position */ + -1, /* (39) attribute ::= THEN */ + -4, /* (40) attribute ::= THEN optrelexpr HEADING expr */ + -3, /* (41) attribute ::= THEN optrelexpr EDGEPT */ + -4, /* (42) attribute ::= GO optrelexpr HEADING expr */ + -3, /* (43) attribute ::= GO optrelexpr EDGEPT */ + -2, /* (44) attribute ::= AT position */ + -1, /* (45) attribute ::= SAME */ + -3, /* (46) attribute ::= SAME AS object */ + -2, /* (47) attribute ::= STRING textposition */ + -1, /* (48) attribute ::= FIT */ + -2, /* (49) attribute ::= BEHIND object */ + -4, /* (50) withclause ::= DOT_E edge AT position */ + -3, /* (51) withclause ::= edge AT position */ + -1, /* (52) numproperty ::= HEIGHT|WIDTH|RADIUS|DIAMETER|THICKNESS */ + -1, /* (53) boolproperty ::= CW */ + -1, /* (54) boolproperty ::= CCW */ + -1, /* (55) boolproperty ::= LARROW */ + -1, /* (56) boolproperty ::= RARROW */ + -1, /* (57) boolproperty ::= LRARROW */ + -1, /* (58) boolproperty ::= INVIS */ + -1, /* (59) boolproperty ::= THICK */ + -1, /* (60) boolproperty ::= THIN */ + -1, /* (61) boolproperty ::= SOLID */ + 0, /* (62) textposition ::= */ + -2, /* (63) textposition ::= textposition CENTER|LJUST|RJUST|ABOVE|BELOW|ITALIC|BOLD|ALIGNED|BIG|SMALL */ + -3, /* (64) position ::= expr COMMA expr */ + -5, /* (65) position ::= place PLUS expr COMMA expr */ + -5, /* (66) position ::= place MINUS expr COMMA expr */ + -7, /* (67) position ::= place PLUS LP expr COMMA expr RP */ + -7, /* (68) position ::= place MINUS LP expr COMMA expr RP */ + -5, /* (69) position ::= LP position COMMA position RP */ + -3, /* (70) position ::= LP position RP */ + -5, /* (71) position ::= expr between position AND position */ + -6, /* (72) position ::= expr LT position COMMA position GT */ + -3, /* (73) position ::= expr ABOVE position */ + -3, /* (74) position ::= expr BELOW position */ + -4, /* (75) position ::= expr LEFT OF position */ + -4, /* (76) position ::= expr RIGHT OF position */ + -6, /* (77) position ::= expr ON HEADING EDGEPT OF position */ + -5, /* (78) position ::= expr HEADING EDGEPT OF position */ + -4, /* (79) position ::= expr EDGEPT OF position */ + -6, /* (80) position ::= expr ON HEADING expr FROM position */ + -5, /* (81) position ::= expr HEADING expr FROM position */ + -3, /* (82) place ::= edge OF object */ + -1, /* (83) place2 ::= object */ + -3, /* (84) place2 ::= object DOT_E edge */ + -4, /* (85) place2 ::= NTH VERTEX OF object */ + -1, /* (86) object ::= nth */ + -3, /* (87) object ::= nth OF|IN object */ + -1, /* (88) objectname ::= THIS */ + -1, /* (89) objectname ::= PLACENAME */ + -3, /* (90) objectname ::= objectname DOT_U PLACENAME */ + -2, /* (91) nth ::= NTH CLASSNAME */ + -3, /* (92) nth ::= NTH LAST CLASSNAME */ + -2, /* (93) nth ::= LAST CLASSNAME */ + -1, /* (94) nth ::= LAST */ + -3, /* (95) nth ::= NTH LB RB */ + -4, /* (96) nth ::= NTH LAST LB RB */ + -3, /* (97) nth ::= LAST LB RB */ + -3, /* (98) expr ::= expr PLUS expr */ + -3, /* (99) expr ::= expr MINUS expr */ + -3, /* (100) expr ::= expr STAR expr */ + -3, /* (101) expr ::= expr SLASH expr */ + -2, /* (102) expr ::= MINUS expr */ + -2, /* (103) expr ::= PLUS expr */ + -3, /* (104) expr ::= LP expr RP */ + -3, /* (105) expr ::= LP FILL|COLOR|THICKNESS RP */ + -1, /* (106) expr ::= NUMBER */ + -1, /* (107) expr ::= ID */ + -4, /* (108) expr ::= FUNC1 LP expr RP */ + -6, /* (109) expr ::= FUNC2 LP expr COMMA expr RP */ + -6, /* (110) expr ::= DIST LP position COMMA position RP */ + -3, /* (111) expr ::= place2 DOT_XY X */ + -3, /* (112) expr ::= place2 DOT_XY Y */ + -3, /* (113) expr ::= object DOT_L numproperty */ + -3, /* (114) expr ::= object DOT_L dashproperty */ + -3, /* (115) expr ::= object DOT_L colorproperty */ + -1, /* (116) lvalue ::= ID */ + -1, /* (117) lvalue ::= FILL */ + -1, /* (118) lvalue ::= COLOR */ + -1, /* (119) lvalue ::= THICKNESS */ + -1, /* (120) rvalue ::= expr */ + -1, /* (121) print ::= PRINT */ + -1, /* (122) prlist ::= pritem */ + -3, /* (123) prlist ::= prlist prsep pritem */ + -1, /* (124) direction ::= UP */ + -1, /* (125) direction ::= DOWN */ + -1, /* (126) direction ::= LEFT */ + -1, /* (127) direction ::= RIGHT */ + -1, /* (128) optrelexpr ::= relexpr */ + -1, /* (129) attribute_list ::= alist */ + 0, /* (130) alist ::= */ + -2, /* (131) alist ::= alist attribute */ + -1, /* (132) attribute ::= boolproperty */ + -2, /* (133) attribute ::= WITH withclause */ + -1, /* (134) go ::= GO */ + 0, /* (135) go ::= */ + -3, /* (136) even ::= UNTIL EVEN WITH */ + -2, /* (137) even ::= EVEN WITH */ + -1, /* (138) dashproperty ::= DOTTED */ + -1, /* (139) dashproperty ::= DASHED */ + -1, /* (140) colorproperty ::= FILL */ + -1, /* (141) colorproperty ::= COLOR */ + -1, /* (142) position ::= place */ + -2, /* (143) between ::= WAY BETWEEN */ + -1, /* (144) between ::= BETWEEN */ + -4, /* (145) between ::= OF THE WAY BETWEEN */ + -1, /* (146) place ::= place2 */ + -1, /* (147) edge ::= CENTER */ + -1, /* (148) edge ::= EDGEPT */ + -1, /* (149) edge ::= TOP */ + -1, /* (150) edge ::= BOTTOM */ + -1, /* (151) edge ::= START */ + -1, /* (152) edge ::= END */ + -1, /* (153) edge ::= RIGHT */ + -1, /* (154) edge ::= LEFT */ + -1, /* (155) object ::= objectname */ +}; + +static void yy_accept(yyParser*); /* Forward Declaration */ + +/* +** Perform a reduce action and the shift that must immediately +** follow the reduce. +** +** The yyLookahead and yyLookaheadToken parameters provide reduce actions +** access to the lookahead token (if any). The yyLookahead will be YYNOCODE +** if the lookahead token has already been consumed. As this procedure is +** only called from one place, optimizing compilers will in-line it, which +** means that the extra parameters have no performance impact. +*/ +static YYACTIONTYPE yy_reduce( + yyParser *yypParser, /* The parser */ + unsigned int yyruleno, /* Number of the rule by which to reduce */ + int yyLookahead, /* Lookahead token, or YYNOCODE if none */ + pik_parserTOKENTYPE yyLookaheadToken /* Value of the lookahead token */ + pik_parserCTX_PDECL /* %extra_context */ +){ + int yygoto; /* The next state */ + YYACTIONTYPE yyact; /* The next action */ + yyStackEntry *yymsp; /* The top of the parser's stack */ + int yysize; /* Amount to pop the stack */ + pik_parserARG_FETCH + (void)yyLookahead; + (void)yyLookaheadToken; + yymsp = yypParser->yytos; + assert( yyruleno<(int)(sizeof(yyRuleName)/sizeof(yyRuleName[0])) ); +#ifndef NDEBUG + if( yyTraceFILE ){ + yysize = yyRuleInfoNRhs[yyruleno]; + if( yysize ){ + fprintf(yyTraceFILE, "%sReduce %d [%s]%s, pop back to state %d.\n", + yyTracePrompt, + yyruleno, yyRuleName[yyruleno], + yyrulenoyytos - yypParser->yystack)>yypParser->yyhwm ){ + yypParser->yyhwm++; + assert( yypParser->yyhwm == (int)(yypParser->yytos - yypParser->yystack)); + } +#endif +#if YYSTACKDEPTH>0 + if( yypParser->yytos>=yypParser->yystackEnd ){ + yyStackOverflow(yypParser); + /* The call to yyStackOverflow() above pops the stack until it is + ** empty, causing the main parser loop to exit. So the return value + ** is never used and does not matter. */ + return 0; + } +#else + if( yypParser->yytos>=&yypParser->yystack[yypParser->yystksz-1] ){ + if( yyGrowStack(yypParser) ){ + yyStackOverflow(yypParser); + /* The call to yyStackOverflow() above pops the stack until it is + ** empty, causing the main parser loop to exit. So the return value + ** is never used and does not matter. */ + return 0; + } + yymsp = yypParser->yytos; + } +#endif + } + + switch( yyruleno ){ + /* Beginning here are the reduction cases. A typical example + ** follows: + ** case 0: + ** #line + ** { ... } // User supplied code + ** #line + ** break; + */ +/********** Begin reduce actions **********************************************/ + YYMINORTYPE yylhsminor; + case 0: /* document ::= statement_list */ +#line 537 "pikchr.y" +{pik_render(p,yymsp[0].minor.yy227);} +#line 2452 "pikchr.c" + break; + case 1: /* statement_list ::= statement */ +#line 540 "pikchr.y" +{ yylhsminor.yy227 = pik_elist_append(p,0,yymsp[0].minor.yy36); } +#line 2457 "pikchr.c" + yymsp[0].minor.yy227 = yylhsminor.yy227; + break; + case 2: /* statement_list ::= statement_list EOL statement */ +#line 542 "pikchr.y" +{ yylhsminor.yy227 = pik_elist_append(p,yymsp[-2].minor.yy227,yymsp[0].minor.yy36); } +#line 2463 "pikchr.c" + yymsp[-2].minor.yy227 = yylhsminor.yy227; + break; + case 3: /* statement ::= */ +#line 545 "pikchr.y" +{ yymsp[1].minor.yy36 = 0; } +#line 2469 "pikchr.c" + break; + case 4: /* statement ::= direction */ +#line 546 "pikchr.y" +{ pik_set_direction(p,yymsp[0].minor.yy0.eCode); yylhsminor.yy36=0; } +#line 2474 "pikchr.c" + yymsp[0].minor.yy36 = yylhsminor.yy36; + break; + case 5: /* statement ::= lvalue ASSIGN rvalue */ +#line 547 "pikchr.y" +{pik_set_var(p,&yymsp[-2].minor.yy0,yymsp[0].minor.yy153,&yymsp[-1].minor.yy0); yylhsminor.yy36=0;} +#line 2480 "pikchr.c" + yymsp[-2].minor.yy36 = yylhsminor.yy36; + break; + case 6: /* statement ::= PLACENAME COLON unnamed_statement */ +#line 549 "pikchr.y" +{ yylhsminor.yy36 = yymsp[0].minor.yy36; pik_elem_setname(p,yymsp[0].minor.yy36,&yymsp[-2].minor.yy0); } +#line 2486 "pikchr.c" + yymsp[-2].minor.yy36 = yylhsminor.yy36; + break; + case 7: /* statement ::= PLACENAME COLON position */ +#line 551 "pikchr.y" +{ yylhsminor.yy36 = pik_elem_new(p,0,0,0); + if(yylhsminor.yy36){ yylhsminor.yy36->ptAt = yymsp[0].minor.yy79; pik_elem_setname(p,yylhsminor.yy36,&yymsp[-2].minor.yy0); }} +#line 2493 "pikchr.c" + yymsp[-2].minor.yy36 = yylhsminor.yy36; + break; + case 8: /* statement ::= unnamed_statement */ +#line 553 "pikchr.y" +{yylhsminor.yy36 = yymsp[0].minor.yy36;} +#line 2499 "pikchr.c" + yymsp[0].minor.yy36 = yylhsminor.yy36; + break; + case 9: /* statement ::= print prlist */ +#line 554 "pikchr.y" +{pik_append(p,"
\n",5); yymsp[-1].minor.yy36=0;} +#line 2505 "pikchr.c" + break; + case 10: /* statement ::= ASSERT LP expr EQ expr RP */ +#line 559 "pikchr.y" +{yymsp[-5].minor.yy36=pik_assert(p,yymsp[-3].minor.yy153,&yymsp[-2].minor.yy0,yymsp[-1].minor.yy153);} +#line 2510 "pikchr.c" + break; + case 11: /* statement ::= ASSERT LP position EQ position RP */ +#line 561 "pikchr.y" +{yymsp[-5].minor.yy36=pik_position_assert(p,&yymsp[-3].minor.yy79,&yymsp[-2].minor.yy0,&yymsp[-1].minor.yy79);} +#line 2515 "pikchr.c" + break; + case 12: /* statement ::= DEFINE ID CODEBLOCK */ +#line 562 "pikchr.y" +{yymsp[-2].minor.yy36=0; pik_add_macro(p,&yymsp[-1].minor.yy0,&yymsp[0].minor.yy0);} +#line 2520 "pikchr.c" + break; + case 13: /* rvalue ::= PLACENAME */ +#line 573 "pikchr.y" +{yylhsminor.yy153 = pik_lookup_color(p,&yymsp[0].minor.yy0);} +#line 2525 "pikchr.c" + yymsp[0].minor.yy153 = yylhsminor.yy153; + break; + case 14: /* pritem ::= FILL */ + case 15: /* pritem ::= COLOR */ yytestcase(yyruleno==15); + case 16: /* pritem ::= THICKNESS */ yytestcase(yyruleno==16); +#line 578 "pikchr.y" +{pik_append_num(p,"",pik_value(p,yymsp[0].minor.yy0.z,yymsp[0].minor.yy0.n,0));} +#line 2533 "pikchr.c" + break; + case 17: /* pritem ::= rvalue */ +#line 581 "pikchr.y" +{pik_append_num(p,"",yymsp[0].minor.yy153);} +#line 2538 "pikchr.c" + break; + case 18: /* pritem ::= STRING */ +#line 582 "pikchr.y" +{pik_append_text(p,yymsp[0].minor.yy0.z+1,yymsp[0].minor.yy0.n-2,0);} +#line 2543 "pikchr.c" + break; + case 19: /* prsep ::= COMMA */ +#line 583 "pikchr.y" +{pik_append(p, " ", 1);} +#line 2548 "pikchr.c" + break; + case 20: /* unnamed_statement ::= basetype attribute_list */ +#line 586 "pikchr.y" +{yylhsminor.yy36 = yymsp[-1].minor.yy36; pik_after_adding_attributes(p,yylhsminor.yy36);} +#line 2553 "pikchr.c" + yymsp[-1].minor.yy36 = yylhsminor.yy36; + break; + case 21: /* basetype ::= CLASSNAME */ +#line 588 "pikchr.y" +{yylhsminor.yy36 = pik_elem_new(p,&yymsp[0].minor.yy0,0,0); } +#line 2559 "pikchr.c" + yymsp[0].minor.yy36 = yylhsminor.yy36; + break; + case 22: /* basetype ::= STRING textposition */ +#line 590 "pikchr.y" +{yymsp[-1].minor.yy0.eCode = yymsp[0].minor.yy164; yylhsminor.yy36 = pik_elem_new(p,0,&yymsp[-1].minor.yy0,0); } +#line 2565 "pikchr.c" + yymsp[-1].minor.yy36 = yylhsminor.yy36; + break; + case 23: /* basetype ::= LB savelist statement_list RB */ +#line 592 "pikchr.y" +{ p->list = yymsp[-2].minor.yy227; yymsp[-3].minor.yy36 = pik_elem_new(p,0,0,yymsp[-1].minor.yy227); if(yymsp[-3].minor.yy36) yymsp[-3].minor.yy36->errTok = yymsp[0].minor.yy0; } +#line 2571 "pikchr.c" + break; + case 24: /* savelist ::= */ +#line 597 "pikchr.y" +{yymsp[1].minor.yy227 = p->list; p->list = 0;} +#line 2576 "pikchr.c" + break; + case 25: /* relexpr ::= expr */ +#line 604 "pikchr.y" +{yylhsminor.yy10.rAbs = yymsp[0].minor.yy153; yylhsminor.yy10.rRel = 0;} +#line 2581 "pikchr.c" + yymsp[0].minor.yy10 = yylhsminor.yy10; + break; + case 26: /* relexpr ::= expr PERCENT */ +#line 605 "pikchr.y" +{yylhsminor.yy10.rAbs = 0; yylhsminor.yy10.rRel = yymsp[-1].minor.yy153/100;} +#line 2587 "pikchr.c" + yymsp[-1].minor.yy10 = yylhsminor.yy10; + break; + case 27: /* optrelexpr ::= */ +#line 607 "pikchr.y" +{yymsp[1].minor.yy10.rAbs = 0; yymsp[1].minor.yy10.rRel = 1.0;} +#line 2593 "pikchr.c" + break; + case 28: /* attribute_list ::= relexpr alist */ +#line 609 "pikchr.y" +{pik_add_direction(p,0,&yymsp[-1].minor.yy10);} +#line 2598 "pikchr.c" + break; + case 29: /* attribute ::= numproperty relexpr */ +#line 613 "pikchr.y" +{ pik_set_numprop(p,&yymsp[-1].minor.yy0,&yymsp[0].minor.yy10); } +#line 2603 "pikchr.c" + break; + case 30: /* attribute ::= dashproperty expr */ +#line 614 "pikchr.y" +{ pik_set_dashed(p,&yymsp[-1].minor.yy0,&yymsp[0].minor.yy153); } +#line 2608 "pikchr.c" + break; + case 31: /* attribute ::= dashproperty */ +#line 615 "pikchr.y" +{ pik_set_dashed(p,&yymsp[0].minor.yy0,0); } +#line 2613 "pikchr.c" + break; + case 32: /* attribute ::= colorproperty rvalue */ +#line 616 "pikchr.y" +{ pik_set_clrprop(p,&yymsp[-1].minor.yy0,yymsp[0].minor.yy153); } +#line 2618 "pikchr.c" + break; + case 33: /* attribute ::= go direction optrelexpr */ +#line 617 "pikchr.y" +{ pik_add_direction(p,&yymsp[-1].minor.yy0,&yymsp[0].minor.yy10);} +#line 2623 "pikchr.c" + break; + case 34: /* attribute ::= go direction even position */ +#line 618 "pikchr.y" +{pik_evenwith(p,&yymsp[-2].minor.yy0,&yymsp[0].minor.yy79);} +#line 2628 "pikchr.c" + break; + case 35: /* attribute ::= CLOSE */ +#line 619 "pikchr.y" +{ pik_close_path(p,&yymsp[0].minor.yy0); } +#line 2633 "pikchr.c" + break; + case 36: /* attribute ::= CHOP */ +#line 620 "pikchr.y" +{ p->cur->bChop = 1; } +#line 2638 "pikchr.c" + break; + case 37: /* attribute ::= FROM position */ +#line 621 "pikchr.y" +{ pik_set_from(p,p->cur,&yymsp[-1].minor.yy0,&yymsp[0].minor.yy79); } +#line 2643 "pikchr.c" + break; + case 38: /* attribute ::= TO position */ +#line 622 "pikchr.y" +{ pik_add_to(p,p->cur,&yymsp[-1].minor.yy0,&yymsp[0].minor.yy79); } +#line 2648 "pikchr.c" + break; + case 39: /* attribute ::= THEN */ +#line 623 "pikchr.y" +{ pik_then(p, &yymsp[0].minor.yy0, p->cur); } +#line 2653 "pikchr.c" + break; + case 40: /* attribute ::= THEN optrelexpr HEADING expr */ + case 42: /* attribute ::= GO optrelexpr HEADING expr */ yytestcase(yyruleno==42); +#line 625 "pikchr.y" +{pik_move_hdg(p,&yymsp[-2].minor.yy10,&yymsp[-1].minor.yy0,yymsp[0].minor.yy153,0,&yymsp[-3].minor.yy0);} +#line 2659 "pikchr.c" + break; + case 41: /* attribute ::= THEN optrelexpr EDGEPT */ + case 43: /* attribute ::= GO optrelexpr EDGEPT */ yytestcase(yyruleno==43); +#line 626 "pikchr.y" +{pik_move_hdg(p,&yymsp[-1].minor.yy10,0,0,&yymsp[0].minor.yy0,&yymsp[-2].minor.yy0);} +#line 2665 "pikchr.c" + break; + case 44: /* attribute ::= AT position */ +#line 631 "pikchr.y" +{ pik_set_at(p,0,&yymsp[0].minor.yy79,&yymsp[-1].minor.yy0); } +#line 2670 "pikchr.c" + break; + case 45: /* attribute ::= SAME */ +#line 633 "pikchr.y" +{pik_same(p,0,&yymsp[0].minor.yy0);} +#line 2675 "pikchr.c" + break; + case 46: /* attribute ::= SAME AS object */ +#line 634 "pikchr.y" +{pik_same(p,yymsp[0].minor.yy36,&yymsp[-2].minor.yy0);} +#line 2680 "pikchr.c" + break; + case 47: /* attribute ::= STRING textposition */ +#line 635 "pikchr.y" +{pik_add_txt(p,&yymsp[-1].minor.yy0,yymsp[0].minor.yy164);} +#line 2685 "pikchr.c" + break; + case 48: /* attribute ::= FIT */ +#line 636 "pikchr.y" +{pik_size_to_fit(p,&yymsp[0].minor.yy0,3); } +#line 2690 "pikchr.c" + break; + case 49: /* attribute ::= BEHIND object */ +#line 637 "pikchr.y" +{pik_behind(p,yymsp[0].minor.yy36);} +#line 2695 "pikchr.c" + break; + case 50: /* withclause ::= DOT_E edge AT position */ + case 51: /* withclause ::= edge AT position */ yytestcase(yyruleno==51); +#line 645 "pikchr.y" +{ pik_set_at(p,&yymsp[-2].minor.yy0,&yymsp[0].minor.yy79,&yymsp[-1].minor.yy0); } +#line 2701 "pikchr.c" + break; + case 52: /* numproperty ::= HEIGHT|WIDTH|RADIUS|DIAMETER|THICKNESS */ +#line 649 "pikchr.y" +{yylhsminor.yy0 = yymsp[0].minor.yy0;} +#line 2706 "pikchr.c" + yymsp[0].minor.yy0 = yylhsminor.yy0; + break; + case 53: /* boolproperty ::= CW */ +#line 660 "pikchr.y" +{p->cur->cw = 1;} +#line 2712 "pikchr.c" + break; + case 54: /* boolproperty ::= CCW */ +#line 661 "pikchr.y" +{p->cur->cw = 0;} +#line 2717 "pikchr.c" + break; + case 55: /* boolproperty ::= LARROW */ +#line 662 "pikchr.y" +{p->cur->larrow=1; p->cur->rarrow=0; } +#line 2722 "pikchr.c" + break; + case 56: /* boolproperty ::= RARROW */ +#line 663 "pikchr.y" +{p->cur->larrow=0; p->cur->rarrow=1; } +#line 2727 "pikchr.c" + break; + case 57: /* boolproperty ::= LRARROW */ +#line 664 "pikchr.y" +{p->cur->larrow=1; p->cur->rarrow=1; } +#line 2732 "pikchr.c" + break; + case 58: /* boolproperty ::= INVIS */ +#line 665 "pikchr.y" +{p->cur->sw = 0.0;} +#line 2737 "pikchr.c" + break; + case 59: /* boolproperty ::= THICK */ +#line 666 "pikchr.y" +{p->cur->sw *= 1.5;} +#line 2742 "pikchr.c" + break; + case 60: /* boolproperty ::= THIN */ +#line 667 "pikchr.y" +{p->cur->sw *= 0.67;} +#line 2747 "pikchr.c" + break; + case 61: /* boolproperty ::= SOLID */ +#line 668 "pikchr.y" +{p->cur->sw = pik_value(p,"thickness",9,0); + p->cur->dotted = p->cur->dashed = 0.0;} +#line 2753 "pikchr.c" + break; + case 62: /* textposition ::= */ +#line 671 "pikchr.y" +{yymsp[1].minor.yy164 = 0;} +#line 2758 "pikchr.c" + break; + case 63: /* textposition ::= textposition CENTER|LJUST|RJUST|ABOVE|BELOW|ITALIC|BOLD|ALIGNED|BIG|SMALL */ +#line 674 "pikchr.y" +{yylhsminor.yy164 = (short int)pik_text_position(yymsp[-1].minor.yy164,&yymsp[0].minor.yy0);} +#line 2763 "pikchr.c" + yymsp[-1].minor.yy164 = yylhsminor.yy164; + break; + case 64: /* position ::= expr COMMA expr */ +#line 677 "pikchr.y" +{yylhsminor.yy79.x=yymsp[-2].minor.yy153; yylhsminor.yy79.y=yymsp[0].minor.yy153;} +#line 2769 "pikchr.c" + yymsp[-2].minor.yy79 = yylhsminor.yy79; + break; + case 65: /* position ::= place PLUS expr COMMA expr */ +#line 679 "pikchr.y" +{yylhsminor.yy79.x=yymsp[-4].minor.yy79.x+yymsp[-2].minor.yy153; yylhsminor.yy79.y=yymsp[-4].minor.yy79.y+yymsp[0].minor.yy153;} +#line 2775 "pikchr.c" + yymsp[-4].minor.yy79 = yylhsminor.yy79; + break; + case 66: /* position ::= place MINUS expr COMMA expr */ +#line 680 "pikchr.y" +{yylhsminor.yy79.x=yymsp[-4].minor.yy79.x-yymsp[-2].minor.yy153; yylhsminor.yy79.y=yymsp[-4].minor.yy79.y-yymsp[0].minor.yy153;} +#line 2781 "pikchr.c" + yymsp[-4].minor.yy79 = yylhsminor.yy79; + break; + case 67: /* position ::= place PLUS LP expr COMMA expr RP */ +#line 682 "pikchr.y" +{yylhsminor.yy79.x=yymsp[-6].minor.yy79.x+yymsp[-3].minor.yy153; yylhsminor.yy79.y=yymsp[-6].minor.yy79.y+yymsp[-1].minor.yy153;} +#line 2787 "pikchr.c" + yymsp[-6].minor.yy79 = yylhsminor.yy79; + break; + case 68: /* position ::= place MINUS LP expr COMMA expr RP */ +#line 684 "pikchr.y" +{yylhsminor.yy79.x=yymsp[-6].minor.yy79.x-yymsp[-3].minor.yy153; yylhsminor.yy79.y=yymsp[-6].minor.yy79.y-yymsp[-1].minor.yy153;} +#line 2793 "pikchr.c" + yymsp[-6].minor.yy79 = yylhsminor.yy79; + break; + case 69: /* position ::= LP position COMMA position RP */ +#line 685 "pikchr.y" +{yymsp[-4].minor.yy79.x=yymsp[-3].minor.yy79.x; yymsp[-4].minor.yy79.y=yymsp[-1].minor.yy79.y;} +#line 2799 "pikchr.c" + break; + case 70: /* position ::= LP position RP */ +#line 686 "pikchr.y" +{yymsp[-2].minor.yy79=yymsp[-1].minor.yy79;} +#line 2804 "pikchr.c" + break; + case 71: /* position ::= expr between position AND position */ +#line 688 "pikchr.y" +{yylhsminor.yy79 = pik_position_between(yymsp[-4].minor.yy153,yymsp[-2].minor.yy79,yymsp[0].minor.yy79);} +#line 2809 "pikchr.c" + yymsp[-4].minor.yy79 = yylhsminor.yy79; + break; + case 72: /* position ::= expr LT position COMMA position GT */ +#line 690 "pikchr.y" +{yylhsminor.yy79 = pik_position_between(yymsp[-5].minor.yy153,yymsp[-3].minor.yy79,yymsp[-1].minor.yy79);} +#line 2815 "pikchr.c" + yymsp[-5].minor.yy79 = yylhsminor.yy79; + break; + case 73: /* position ::= expr ABOVE position */ +#line 691 "pikchr.y" +{yylhsminor.yy79=yymsp[0].minor.yy79; yylhsminor.yy79.y += yymsp[-2].minor.yy153;} +#line 2821 "pikchr.c" + yymsp[-2].minor.yy79 = yylhsminor.yy79; + break; + case 74: /* position ::= expr BELOW position */ +#line 692 "pikchr.y" +{yylhsminor.yy79=yymsp[0].minor.yy79; yylhsminor.yy79.y -= yymsp[-2].minor.yy153;} +#line 2827 "pikchr.c" + yymsp[-2].minor.yy79 = yylhsminor.yy79; + break; + case 75: /* position ::= expr LEFT OF position */ +#line 693 "pikchr.y" +{yylhsminor.yy79=yymsp[0].minor.yy79; yylhsminor.yy79.x -= yymsp[-3].minor.yy153;} +#line 2833 "pikchr.c" + yymsp[-3].minor.yy79 = yylhsminor.yy79; + break; + case 76: /* position ::= expr RIGHT OF position */ +#line 694 "pikchr.y" +{yylhsminor.yy79=yymsp[0].minor.yy79; yylhsminor.yy79.x += yymsp[-3].minor.yy153;} +#line 2839 "pikchr.c" + yymsp[-3].minor.yy79 = yylhsminor.yy79; + break; + case 77: /* position ::= expr ON HEADING EDGEPT OF position */ +#line 696 "pikchr.y" +{yylhsminor.yy79 = pik_position_at_hdg(yymsp[-5].minor.yy153,&yymsp[-2].minor.yy0,yymsp[0].minor.yy79);} +#line 2845 "pikchr.c" + yymsp[-5].minor.yy79 = yylhsminor.yy79; + break; + case 78: /* position ::= expr HEADING EDGEPT OF position */ +#line 698 "pikchr.y" +{yylhsminor.yy79 = pik_position_at_hdg(yymsp[-4].minor.yy153,&yymsp[-2].minor.yy0,yymsp[0].minor.yy79);} +#line 2851 "pikchr.c" + yymsp[-4].minor.yy79 = yylhsminor.yy79; + break; + case 79: /* position ::= expr EDGEPT OF position */ +#line 700 "pikchr.y" +{yylhsminor.yy79 = pik_position_at_hdg(yymsp[-3].minor.yy153,&yymsp[-2].minor.yy0,yymsp[0].minor.yy79);} +#line 2857 "pikchr.c" + yymsp[-3].minor.yy79 = yylhsminor.yy79; + break; + case 80: /* position ::= expr ON HEADING expr FROM position */ +#line 702 "pikchr.y" +{yylhsminor.yy79 = pik_position_at_angle(yymsp[-5].minor.yy153,yymsp[-2].minor.yy153,yymsp[0].minor.yy79);} +#line 2863 "pikchr.c" + yymsp[-5].minor.yy79 = yylhsminor.yy79; + break; + case 81: /* position ::= expr HEADING expr FROM position */ +#line 704 "pikchr.y" +{yylhsminor.yy79 = pik_position_at_angle(yymsp[-4].minor.yy153,yymsp[-2].minor.yy153,yymsp[0].minor.yy79);} +#line 2869 "pikchr.c" + yymsp[-4].minor.yy79 = yylhsminor.yy79; + break; + case 82: /* place ::= edge OF object */ +#line 716 "pikchr.y" +{yylhsminor.yy79 = pik_place_of_elem(p,yymsp[0].minor.yy36,&yymsp[-2].minor.yy0);} +#line 2875 "pikchr.c" + yymsp[-2].minor.yy79 = yylhsminor.yy79; + break; + case 83: /* place2 ::= object */ +#line 717 "pikchr.y" +{yylhsminor.yy79 = pik_place_of_elem(p,yymsp[0].minor.yy36,0);} +#line 2881 "pikchr.c" + yymsp[0].minor.yy79 = yylhsminor.yy79; + break; + case 84: /* place2 ::= object DOT_E edge */ +#line 718 "pikchr.y" +{yylhsminor.yy79 = pik_place_of_elem(p,yymsp[-2].minor.yy36,&yymsp[0].minor.yy0);} +#line 2887 "pikchr.c" + yymsp[-2].minor.yy79 = yylhsminor.yy79; + break; + case 85: /* place2 ::= NTH VERTEX OF object */ +#line 719 "pikchr.y" +{yylhsminor.yy79 = pik_nth_vertex(p,&yymsp[-3].minor.yy0,&yymsp[-2].minor.yy0,yymsp[0].minor.yy36);} +#line 2893 "pikchr.c" + yymsp[-3].minor.yy79 = yylhsminor.yy79; + break; + case 86: /* object ::= nth */ +#line 731 "pikchr.y" +{yylhsminor.yy36 = pik_find_nth(p,0,&yymsp[0].minor.yy0);} +#line 2899 "pikchr.c" + yymsp[0].minor.yy36 = yylhsminor.yy36; + break; + case 87: /* object ::= nth OF|IN object */ +#line 732 "pikchr.y" +{yylhsminor.yy36 = pik_find_nth(p,yymsp[0].minor.yy36,&yymsp[-2].minor.yy0);} +#line 2905 "pikchr.c" + yymsp[-2].minor.yy36 = yylhsminor.yy36; + break; + case 88: /* objectname ::= THIS */ +#line 734 "pikchr.y" +{yymsp[0].minor.yy36 = p->cur;} +#line 2911 "pikchr.c" + break; + case 89: /* objectname ::= PLACENAME */ +#line 735 "pikchr.y" +{yylhsminor.yy36 = pik_find_byname(p,0,&yymsp[0].minor.yy0);} +#line 2916 "pikchr.c" + yymsp[0].minor.yy36 = yylhsminor.yy36; + break; + case 90: /* objectname ::= objectname DOT_U PLACENAME */ +#line 737 "pikchr.y" +{yylhsminor.yy36 = pik_find_byname(p,yymsp[-2].minor.yy36,&yymsp[0].minor.yy0);} +#line 2922 "pikchr.c" + yymsp[-2].minor.yy36 = yylhsminor.yy36; + break; + case 91: /* nth ::= NTH CLASSNAME */ +#line 739 "pikchr.y" +{yylhsminor.yy0=yymsp[0].minor.yy0; yylhsminor.yy0.eCode = pik_nth_value(p,&yymsp[-1].minor.yy0); } +#line 2928 "pikchr.c" + yymsp[-1].minor.yy0 = yylhsminor.yy0; + break; + case 92: /* nth ::= NTH LAST CLASSNAME */ +#line 740 "pikchr.y" +{yylhsminor.yy0=yymsp[0].minor.yy0; yylhsminor.yy0.eCode = -pik_nth_value(p,&yymsp[-2].minor.yy0); } +#line 2934 "pikchr.c" + yymsp[-2].minor.yy0 = yylhsminor.yy0; + break; + case 93: /* nth ::= LAST CLASSNAME */ +#line 741 "pikchr.y" +{yymsp[-1].minor.yy0=yymsp[0].minor.yy0; yymsp[-1].minor.yy0.eCode = -1;} +#line 2940 "pikchr.c" + break; + case 94: /* nth ::= LAST */ +#line 742 "pikchr.y" +{yylhsminor.yy0=yymsp[0].minor.yy0; yylhsminor.yy0.eCode = -1;} +#line 2945 "pikchr.c" + yymsp[0].minor.yy0 = yylhsminor.yy0; + break; + case 95: /* nth ::= NTH LB RB */ +#line 743 "pikchr.y" +{yylhsminor.yy0=yymsp[-1].minor.yy0; yylhsminor.yy0.eCode = pik_nth_value(p,&yymsp[-2].minor.yy0);} +#line 2951 "pikchr.c" + yymsp[-2].minor.yy0 = yylhsminor.yy0; + break; + case 96: /* nth ::= NTH LAST LB RB */ +#line 744 "pikchr.y" +{yylhsminor.yy0=yymsp[-1].minor.yy0; yylhsminor.yy0.eCode = -pik_nth_value(p,&yymsp[-3].minor.yy0);} +#line 2957 "pikchr.c" + yymsp[-3].minor.yy0 = yylhsminor.yy0; + break; + case 97: /* nth ::= LAST LB RB */ +#line 745 "pikchr.y" +{yymsp[-2].minor.yy0=yymsp[-1].minor.yy0; yymsp[-2].minor.yy0.eCode = -1; } +#line 2963 "pikchr.c" + break; + case 98: /* expr ::= expr PLUS expr */ +#line 747 "pikchr.y" +{yylhsminor.yy153=yymsp[-2].minor.yy153+yymsp[0].minor.yy153;} +#line 2968 "pikchr.c" + yymsp[-2].minor.yy153 = yylhsminor.yy153; + break; + case 99: /* expr ::= expr MINUS expr */ +#line 748 "pikchr.y" +{yylhsminor.yy153=yymsp[-2].minor.yy153-yymsp[0].minor.yy153;} +#line 2974 "pikchr.c" + yymsp[-2].minor.yy153 = yylhsminor.yy153; + break; + case 100: /* expr ::= expr STAR expr */ +#line 749 "pikchr.y" +{yylhsminor.yy153=yymsp[-2].minor.yy153*yymsp[0].minor.yy153;} +#line 2980 "pikchr.c" + yymsp[-2].minor.yy153 = yylhsminor.yy153; + break; + case 101: /* expr ::= expr SLASH expr */ +#line 750 "pikchr.y" +{ + if( yymsp[0].minor.yy153==0.0 ){ pik_error(p, &yymsp[-1].minor.yy0, "division by zero"); yylhsminor.yy153 = 0.0; } + else{ yylhsminor.yy153 = yymsp[-2].minor.yy153/yymsp[0].minor.yy153; } +} +#line 2989 "pikchr.c" + yymsp[-2].minor.yy153 = yylhsminor.yy153; + break; + case 102: /* expr ::= MINUS expr */ +#line 754 "pikchr.y" +{yymsp[-1].minor.yy153=-yymsp[0].minor.yy153;} +#line 2995 "pikchr.c" + break; + case 103: /* expr ::= PLUS expr */ +#line 755 "pikchr.y" +{yymsp[-1].minor.yy153=yymsp[0].minor.yy153;} +#line 3000 "pikchr.c" + break; + case 104: /* expr ::= LP expr RP */ +#line 756 "pikchr.y" +{yymsp[-2].minor.yy153=yymsp[-1].minor.yy153;} +#line 3005 "pikchr.c" + break; + case 105: /* expr ::= LP FILL|COLOR|THICKNESS RP */ +#line 757 "pikchr.y" +{yymsp[-2].minor.yy153=pik_get_var(p,&yymsp[-1].minor.yy0);} +#line 3010 "pikchr.c" + break; + case 106: /* expr ::= NUMBER */ +#line 758 "pikchr.y" +{yylhsminor.yy153=pik_atof(&yymsp[0].minor.yy0);} +#line 3015 "pikchr.c" + yymsp[0].minor.yy153 = yylhsminor.yy153; + break; + case 107: /* expr ::= ID */ +#line 759 "pikchr.y" +{yylhsminor.yy153=pik_get_var(p,&yymsp[0].minor.yy0);} +#line 3021 "pikchr.c" + yymsp[0].minor.yy153 = yylhsminor.yy153; + break; + case 108: /* expr ::= FUNC1 LP expr RP */ +#line 760 "pikchr.y" +{yylhsminor.yy153 = pik_func(p,&yymsp[-3].minor.yy0,yymsp[-1].minor.yy153,0.0);} +#line 3027 "pikchr.c" + yymsp[-3].minor.yy153 = yylhsminor.yy153; + break; + case 109: /* expr ::= FUNC2 LP expr COMMA expr RP */ +#line 761 "pikchr.y" +{yylhsminor.yy153 = pik_func(p,&yymsp[-5].minor.yy0,yymsp[-3].minor.yy153,yymsp[-1].minor.yy153);} +#line 3033 "pikchr.c" + yymsp[-5].minor.yy153 = yylhsminor.yy153; + break; + case 110: /* expr ::= DIST LP position COMMA position RP */ +#line 762 "pikchr.y" +{yymsp[-5].minor.yy153 = pik_dist(&yymsp[-3].minor.yy79,&yymsp[-1].minor.yy79);} +#line 3039 "pikchr.c" + break; + case 111: /* expr ::= place2 DOT_XY X */ +#line 763 "pikchr.y" +{yylhsminor.yy153 = yymsp[-2].minor.yy79.x;} +#line 3044 "pikchr.c" + yymsp[-2].minor.yy153 = yylhsminor.yy153; + break; + case 112: /* expr ::= place2 DOT_XY Y */ +#line 764 "pikchr.y" +{yylhsminor.yy153 = yymsp[-2].minor.yy79.y;} +#line 3050 "pikchr.c" + yymsp[-2].minor.yy153 = yylhsminor.yy153; + break; + case 113: /* expr ::= object DOT_L numproperty */ + case 114: /* expr ::= object DOT_L dashproperty */ yytestcase(yyruleno==114); + case 115: /* expr ::= object DOT_L colorproperty */ yytestcase(yyruleno==115); +#line 765 "pikchr.y" +{yylhsminor.yy153=pik_property_of(yymsp[-2].minor.yy36,&yymsp[0].minor.yy0);} +#line 3058 "pikchr.c" + yymsp[-2].minor.yy153 = yylhsminor.yy153; + break; + default: + /* (116) lvalue ::= ID */ yytestcase(yyruleno==116); + /* (117) lvalue ::= FILL */ yytestcase(yyruleno==117); + /* (118) lvalue ::= COLOR */ yytestcase(yyruleno==118); + /* (119) lvalue ::= THICKNESS */ yytestcase(yyruleno==119); + /* (120) rvalue ::= expr */ yytestcase(yyruleno==120); + /* (121) print ::= PRINT */ yytestcase(yyruleno==121); + /* (122) prlist ::= pritem (OPTIMIZED OUT) */ assert(yyruleno!=122); + /* (123) prlist ::= prlist prsep pritem */ yytestcase(yyruleno==123); + /* (124) direction ::= UP */ yytestcase(yyruleno==124); + /* (125) direction ::= DOWN */ yytestcase(yyruleno==125); + /* (126) direction ::= LEFT */ yytestcase(yyruleno==126); + /* (127) direction ::= RIGHT */ yytestcase(yyruleno==127); + /* (128) optrelexpr ::= relexpr (OPTIMIZED OUT) */ assert(yyruleno!=128); + /* (129) attribute_list ::= alist */ yytestcase(yyruleno==129); + /* (130) alist ::= */ yytestcase(yyruleno==130); + /* (131) alist ::= alist attribute */ yytestcase(yyruleno==131); + /* (132) attribute ::= boolproperty (OPTIMIZED OUT) */ assert(yyruleno!=132); + /* (133) attribute ::= WITH withclause */ yytestcase(yyruleno==133); + /* (134) go ::= GO */ yytestcase(yyruleno==134); + /* (135) go ::= */ yytestcase(yyruleno==135); + /* (136) even ::= UNTIL EVEN WITH */ yytestcase(yyruleno==136); + /* (137) even ::= EVEN WITH */ yytestcase(yyruleno==137); + /* (138) dashproperty ::= DOTTED */ yytestcase(yyruleno==138); + /* (139) dashproperty ::= DASHED */ yytestcase(yyruleno==139); + /* (140) colorproperty ::= FILL */ yytestcase(yyruleno==140); + /* (141) colorproperty ::= COLOR */ yytestcase(yyruleno==141); + /* (142) position ::= place */ yytestcase(yyruleno==142); + /* (143) between ::= WAY BETWEEN */ yytestcase(yyruleno==143); + /* (144) between ::= BETWEEN */ yytestcase(yyruleno==144); + /* (145) between ::= OF THE WAY BETWEEN */ yytestcase(yyruleno==145); + /* (146) place ::= place2 */ yytestcase(yyruleno==146); + /* (147) edge ::= CENTER */ yytestcase(yyruleno==147); + /* (148) edge ::= EDGEPT */ yytestcase(yyruleno==148); + /* (149) edge ::= TOP */ yytestcase(yyruleno==149); + /* (150) edge ::= BOTTOM */ yytestcase(yyruleno==150); + /* (151) edge ::= START */ yytestcase(yyruleno==151); + /* (152) edge ::= END */ yytestcase(yyruleno==152); + /* (153) edge ::= RIGHT */ yytestcase(yyruleno==153); + /* (154) edge ::= LEFT */ yytestcase(yyruleno==154); + /* (155) object ::= objectname */ yytestcase(yyruleno==155); + break; +/********** End reduce actions ************************************************/ + }; + assert( yyrulenoYY_MAX_SHIFT && yyact<=YY_MAX_SHIFTREDUCE) ); + + /* It is not possible for a REDUCE to be followed by an error */ + assert( yyact!=YY_ERROR_ACTION ); + + yymsp += yysize+1; + yypParser->yytos = yymsp; + yymsp->stateno = (YYACTIONTYPE)yyact; + yymsp->major = (YYCODETYPE)yygoto; + yyTraceShift(yypParser, yyact, "... then shift"); + return yyact; +} + +/* +** The following code executes when the parse fails +*/ +#ifndef YYNOERRORRECOVERY +static void yy_parse_failed( + yyParser *yypParser /* The parser */ +){ + pik_parserARG_FETCH + pik_parserCTX_FETCH +#ifndef NDEBUG + if( yyTraceFILE ){ + fprintf(yyTraceFILE,"%sFail!\n",yyTracePrompt); + } +#endif + while( yypParser->yytos>yypParser->yystack ) yy_pop_parser_stack(yypParser); + /* Here code is inserted which will be executed whenever the + ** parser fails */ +/************ Begin %parse_failure code ***************************************/ +/************ End %parse_failure code *****************************************/ + pik_parserARG_STORE /* Suppress warning about unused %extra_argument variable */ + pik_parserCTX_STORE +} +#endif /* YYNOERRORRECOVERY */ + +/* +** The following code executes when a syntax error first occurs. +*/ +static void yy_syntax_error( + yyParser *yypParser, /* The parser */ + int yymajor, /* The major type of the error token */ + pik_parserTOKENTYPE yyminor /* The minor type of the error token */ +){ + pik_parserARG_FETCH + pik_parserCTX_FETCH +#define TOKEN yyminor +/************ Begin %syntax_error code ****************************************/ +#line 525 "pikchr.y" + + if( TOKEN.z && TOKEN.z[0] ){ + pik_error(p, &TOKEN, "syntax error"); + }else{ + pik_error(p, 0, "syntax error"); + } + UNUSED_PARAMETER(yymajor); +#line 3169 "pikchr.c" +/************ End %syntax_error code ******************************************/ + pik_parserARG_STORE /* Suppress warning about unused %extra_argument variable */ + pik_parserCTX_STORE +} + +/* +** The following is executed when the parser accepts +*/ +static void yy_accept( + yyParser *yypParser /* The parser */ +){ + pik_parserARG_FETCH + pik_parserCTX_FETCH +#ifndef NDEBUG + if( yyTraceFILE ){ + fprintf(yyTraceFILE,"%sAccept!\n",yyTracePrompt); + } +#endif +#ifndef YYNOERRORRECOVERY + yypParser->yyerrcnt = -1; +#endif + assert( yypParser->yytos==yypParser->yystack ); + /* Here code is inserted which will be executed whenever the + ** parser accepts */ +/*********** Begin %parse_accept code *****************************************/ +/*********** End %parse_accept code *******************************************/ + pik_parserARG_STORE /* Suppress warning about unused %extra_argument variable */ + pik_parserCTX_STORE +} + +/* The main parser program. +** The first argument is a pointer to a structure obtained from +** "pik_parserAlloc" which describes the current state of the parser. +** The second argument is the major token number. The third is +** the minor token. The fourth optional argument is whatever the +** user wants (and specified in the grammar) and is available for +** use by the action routines. +** +** Inputs: +**
    +**
  • A pointer to the parser (an opaque structure.) +**
  • The major token number. +**
  • The minor token number. +**
  • An option argument of a grammar-specified type. +**
+** +** Outputs: +** None. +*/ +void pik_parser( + void *yyp, /* The parser */ + int yymajor, /* The major token code number */ + pik_parserTOKENTYPE yyminor /* The value for the token */ + pik_parserARG_PDECL /* Optional %extra_argument parameter */ +){ + YYMINORTYPE yyminorunion; + YYACTIONTYPE yyact; /* The parser action. */ +#if !defined(YYERRORSYMBOL) && !defined(YYNOERRORRECOVERY) + int yyendofinput; /* True if we are at the end of input */ +#endif +#ifdef YYERRORSYMBOL + int yyerrorhit = 0; /* True if yymajor has invoked an error */ +#endif + yyParser *yypParser = (yyParser*)yyp; /* The parser */ + pik_parserCTX_FETCH + pik_parserARG_STORE + + assert( yypParser->yytos!=0 ); +#if !defined(YYERRORSYMBOL) && !defined(YYNOERRORRECOVERY) + yyendofinput = (yymajor==0); +#endif + + yyact = yypParser->yytos->stateno; +#ifndef NDEBUG + if( yyTraceFILE ){ + if( yyact < YY_MIN_REDUCE ){ + fprintf(yyTraceFILE,"%sInput '%s' in state %d\n", + yyTracePrompt,yyTokenName[yymajor],yyact); + }else{ + fprintf(yyTraceFILE,"%sInput '%s' with pending reduce %d\n", + yyTracePrompt,yyTokenName[yymajor],yyact-YY_MIN_REDUCE); + } + } +#endif + + do{ + assert( yyact==yypParser->yytos->stateno ); + yyact = yy_find_shift_action((YYCODETYPE)yymajor,yyact); + if( yyact >= YY_MIN_REDUCE ){ + yyact = yy_reduce(yypParser,yyact-YY_MIN_REDUCE,yymajor, + yyminor pik_parserCTX_PARAM); + }else if( yyact <= YY_MAX_SHIFTREDUCE ){ + yy_shift(yypParser,yyact,(YYCODETYPE)yymajor,yyminor); +#ifndef YYNOERRORRECOVERY + yypParser->yyerrcnt--; +#endif + break; + }else if( yyact==YY_ACCEPT_ACTION ){ + yypParser->yytos--; + yy_accept(yypParser); + return; + }else{ + assert( yyact == YY_ERROR_ACTION ); + yyminorunion.yy0 = yyminor; +#ifdef YYERRORSYMBOL + int yymx; +#endif +#ifndef NDEBUG + if( yyTraceFILE ){ + fprintf(yyTraceFILE,"%sSyntax Error!\n",yyTracePrompt); + } +#endif +#ifdef YYERRORSYMBOL + /* A syntax error has occurred. + ** The response to an error depends upon whether or not the + ** grammar defines an error token "ERROR". + ** + ** This is what we do if the grammar does define ERROR: + ** + ** * Call the %syntax_error function. + ** + ** * Begin popping the stack until we enter a state where + ** it is legal to shift the error symbol, then shift + ** the error symbol. + ** + ** * Set the error count to three. + ** + ** * Begin accepting and shifting new tokens. No new error + ** processing will occur until three tokens have been + ** shifted successfully. + ** + */ + if( yypParser->yyerrcnt<0 ){ + yy_syntax_error(yypParser,yymajor,yyminor); + } + yymx = yypParser->yytos->major; + if( yymx==YYERRORSYMBOL || yyerrorhit ){ +#ifndef NDEBUG + if( yyTraceFILE ){ + fprintf(yyTraceFILE,"%sDiscard input token %s\n", + yyTracePrompt,yyTokenName[yymajor]); + } +#endif + yy_destructor(yypParser, (YYCODETYPE)yymajor, &yyminorunion); + yymajor = YYNOCODE; + }else{ + while( yypParser->yytos >= yypParser->yystack + && (yyact = yy_find_reduce_action( + yypParser->yytos->stateno, + YYERRORSYMBOL)) > YY_MAX_SHIFTREDUCE + ){ + yy_pop_parser_stack(yypParser); + } + if( yypParser->yytos < yypParser->yystack || yymajor==0 ){ + yy_destructor(yypParser,(YYCODETYPE)yymajor,&yyminorunion); + yy_parse_failed(yypParser); +#ifndef YYNOERRORRECOVERY + yypParser->yyerrcnt = -1; +#endif + yymajor = YYNOCODE; + }else if( yymx!=YYERRORSYMBOL ){ + yy_shift(yypParser,yyact,YYERRORSYMBOL,yyminor); + } + } + yypParser->yyerrcnt = 3; + yyerrorhit = 1; + if( yymajor==YYNOCODE ) break; + yyact = yypParser->yytos->stateno; +#elif defined(YYNOERRORRECOVERY) + /* If the YYNOERRORRECOVERY macro is defined, then do not attempt to + ** do any kind of error recovery. Instead, simply invoke the syntax + ** error routine and continue going as if nothing had happened. + ** + ** Applications can set this macro (for example inside %include) if + ** they intend to abandon the parse upon the first syntax error seen. + */ + yy_syntax_error(yypParser,yymajor, yyminor); + yy_destructor(yypParser,(YYCODETYPE)yymajor,&yyminorunion); + break; +#else /* YYERRORSYMBOL is not defined */ + /* This is what we do if the grammar does not define ERROR: + ** + ** * Report an error message, and throw away the input token. + ** + ** * If the input token is $, then fail the parse. + ** + ** As before, subsequent error messages are suppressed until + ** three input tokens have been successfully shifted. + */ + if( yypParser->yyerrcnt<=0 ){ + yy_syntax_error(yypParser,yymajor, yyminor); + } + yypParser->yyerrcnt = 3; + yy_destructor(yypParser,(YYCODETYPE)yymajor,&yyminorunion); + if( yyendofinput ){ + yy_parse_failed(yypParser); +#ifndef YYNOERRORRECOVERY + yypParser->yyerrcnt = -1; +#endif + } + break; +#endif + } + }while( yypParser->yytos>yypParser->yystack ); +#ifndef NDEBUG + if( yyTraceFILE ){ + yyStackEntry *i; + char cDiv = '['; + fprintf(yyTraceFILE,"%sReturn. Stack=",yyTracePrompt); + for(i=&yypParser->yystack[1]; i<=yypParser->yytos; i++){ + fprintf(yyTraceFILE,"%c%s", cDiv, yyTokenName[i->major]); + cDiv = ' '; + } + fprintf(yyTraceFILE,"]\n"); + } +#endif + return; +} + +/* +** Return the fallback token corresponding to canonical token iToken, or +** 0 if iToken has no fallback. +*/ +int pik_parserFallback(int iToken){ +#ifdef YYFALLBACK + assert( iToken<(int)(sizeof(yyFallback)/sizeof(yyFallback[0])) ); + return yyFallback[iToken]; +#else + (void)iToken; + return 0; +#endif +} +#line 770 "pikchr.y" + + + +/* Chart of the 148 official CSS color names with their +** corresponding RGB values thru Color Module Level 4: +** https://developer.mozilla.org/en-US/docs/Web/CSS/color_value +** +** Two new names "None" and "Off" are added with a value +** of -1. +*/ +static const struct { + const char *zName; /* Name of the color */ + int val; /* RGB value */ +} aColor[] = { + { "AliceBlue", 0xf0f8ff }, + { "AntiqueWhite", 0xfaebd7 }, + { "Aqua", 0x00ffff }, + { "Aquamarine", 0x7fffd4 }, + { "Azure", 0xf0ffff }, + { "Beige", 0xf5f5dc }, + { "Bisque", 0xffe4c4 }, + { "Black", 0x000000 }, + { "BlanchedAlmond", 0xffebcd }, + { "Blue", 0x0000ff }, + { "BlueViolet", 0x8a2be2 }, + { "Brown", 0xa52a2a }, + { "BurlyWood", 0xdeb887 }, + { "CadetBlue", 0x5f9ea0 }, + { "Chartreuse", 0x7fff00 }, + { "Chocolate", 0xd2691e }, + { "Coral", 0xff7f50 }, + { "CornflowerBlue", 0x6495ed }, + { "Cornsilk", 0xfff8dc }, + { "Crimson", 0xdc143c }, + { "Cyan", 0x00ffff }, + { "DarkBlue", 0x00008b }, + { "DarkCyan", 0x008b8b }, + { "DarkGoldenrod", 0xb8860b }, + { "DarkGray", 0xa9a9a9 }, + { "DarkGreen", 0x006400 }, + { "DarkGrey", 0xa9a9a9 }, + { "DarkKhaki", 0xbdb76b }, + { "DarkMagenta", 0x8b008b }, + { "DarkOliveGreen", 0x556b2f }, + { "DarkOrange", 0xff8c00 }, + { "DarkOrchid", 0x9932cc }, + { "DarkRed", 0x8b0000 }, + { "DarkSalmon", 0xe9967a }, + { "DarkSeaGreen", 0x8fbc8f }, + { "DarkSlateBlue", 0x483d8b }, + { "DarkSlateGray", 0x2f4f4f }, + { "DarkSlateGrey", 0x2f4f4f }, + { "DarkTurquoise", 0x00ced1 }, + { "DarkViolet", 0x9400d3 }, + { "DeepPink", 0xff1493 }, + { "DeepSkyBlue", 0x00bfff }, + { "DimGray", 0x696969 }, + { "DimGrey", 0x696969 }, + { "DodgerBlue", 0x1e90ff }, + { "Firebrick", 0xb22222 }, + { "FloralWhite", 0xfffaf0 }, + { "ForestGreen", 0x228b22 }, + { "Fuchsia", 0xff00ff }, + { "Gainsboro", 0xdcdcdc }, + { "GhostWhite", 0xf8f8ff }, + { "Gold", 0xffd700 }, + { "Goldenrod", 0xdaa520 }, + { "Gray", 0x808080 }, + { "Green", 0x008000 }, + { "GreenYellow", 0xadff2f }, + { "Grey", 0x808080 }, + { "Honeydew", 0xf0fff0 }, + { "HotPink", 0xff69b4 }, + { "IndianRed", 0xcd5c5c }, + { "Indigo", 0x4b0082 }, + { "Ivory", 0xfffff0 }, + { "Khaki", 0xf0e68c }, + { "Lavender", 0xe6e6fa }, + { "LavenderBlush", 0xfff0f5 }, + { "LawnGreen", 0x7cfc00 }, + { "LemonChiffon", 0xfffacd }, + { "LightBlue", 0xadd8e6 }, + { "LightCoral", 0xf08080 }, + { "LightCyan", 0xe0ffff }, + { "LightGoldenrodYellow", 0xfafad2 }, + { "LightGray", 0xd3d3d3 }, + { "LightGreen", 0x90ee90 }, + { "LightGrey", 0xd3d3d3 }, + { "LightPink", 0xffb6c1 }, + { "LightSalmon", 0xffa07a }, + { "LightSeaGreen", 0x20b2aa }, + { "LightSkyBlue", 0x87cefa }, + { "LightSlateGray", 0x778899 }, + { "LightSlateGrey", 0x778899 }, + { "LightSteelBlue", 0xb0c4de }, + { "LightYellow", 0xffffe0 }, + { "Lime", 0x00ff00 }, + { "LimeGreen", 0x32cd32 }, + { "Linen", 0xfaf0e6 }, + { "Magenta", 0xff00ff }, + { "Maroon", 0x800000 }, + { "MediumAquamarine", 0x66cdaa }, + { "MediumBlue", 0x0000cd }, + { "MediumOrchid", 0xba55d3 }, + { "MediumPurple", 0x9370db }, + { "MediumSeaGreen", 0x3cb371 }, + { "MediumSlateBlue", 0x7b68ee }, + { "MediumSpringGreen", 0x00fa9a }, + { "MediumTurquoise", 0x48d1cc }, + { "MediumVioletRed", 0xc71585 }, + { "MidnightBlue", 0x191970 }, + { "MintCream", 0xf5fffa }, + { "MistyRose", 0xffe4e1 }, + { "Moccasin", 0xffe4b5 }, + { "NavajoWhite", 0xffdead }, + { "Navy", 0x000080 }, + { "None", -1 }, /* Non-standard addition */ + { "Off", -1 }, /* Non-standard addition */ + { "OldLace", 0xfdf5e6 }, + { "Olive", 0x808000 }, + { "OliveDrab", 0x6b8e23 }, + { "Orange", 0xffa500 }, + { "OrangeRed", 0xff4500 }, + { "Orchid", 0xda70d6 }, + { "PaleGoldenrod", 0xeee8aa }, + { "PaleGreen", 0x98fb98 }, + { "PaleTurquoise", 0xafeeee }, + { "PaleVioletRed", 0xdb7093 }, + { "PapayaWhip", 0xffefd5 }, + { "PeachPuff", 0xffdab9 }, + { "Peru", 0xcd853f }, + { "Pink", 0xffc0cb }, + { "Plum", 0xdda0dd }, + { "PowderBlue", 0xb0e0e6 }, + { "Purple", 0x800080 }, + { "RebeccaPurple", 0x663399 }, + { "Red", 0xff0000 }, + { "RosyBrown", 0xbc8f8f }, + { "RoyalBlue", 0x4169e1 }, + { "SaddleBrown", 0x8b4513 }, + { "Salmon", 0xfa8072 }, + { "SandyBrown", 0xf4a460 }, + { "SeaGreen", 0x2e8b57 }, + { "Seashell", 0xfff5ee }, + { "Sienna", 0xa0522d }, + { "Silver", 0xc0c0c0 }, + { "SkyBlue", 0x87ceeb }, + { "SlateBlue", 0x6a5acd }, + { "SlateGray", 0x708090 }, + { "SlateGrey", 0x708090 }, + { "Snow", 0xfffafa }, + { "SpringGreen", 0x00ff7f }, + { "SteelBlue", 0x4682b4 }, + { "Tan", 0xd2b48c }, + { "Teal", 0x008080 }, + { "Thistle", 0xd8bfd8 }, + { "Tomato", 0xff6347 }, + { "Turquoise", 0x40e0d0 }, + { "Violet", 0xee82ee }, + { "Wheat", 0xf5deb3 }, + { "White", 0xffffff }, + { "WhiteSmoke", 0xf5f5f5 }, + { "Yellow", 0xffff00 }, + { "YellowGreen", 0x9acd32 }, +}; + +/* Built-in variable names. +** +** This array is constant. When a script changes the value of one of +** these built-ins, a new PVar record is added at the head of +** the Pik.pVar list, which is searched first. Thus the new PVar entry +** will override this default value. +** +** Units are in inches, except for "color" and "fill" which are +** interpreted as 24-bit RGB values. +** +** Binary search used. Must be kept in sorted order. +*/ +static const struct { const char *zName; PNum val; } aBuiltin[] = { + { "arcrad", 0.25 }, + { "arrowhead", 2.0 }, + { "arrowht", 0.08 }, + { "arrowwid", 0.06 }, + { "boxht", 0.5 }, + { "boxrad", 0.0 }, + { "boxwid", 0.75 }, + { "charht", 0.14 }, + { "charwid", 0.08 }, + { "circlerad", 0.25 }, + { "color", 0.0 }, + { "cylht", 0.5 }, + { "cylrad", 0.075 }, + { "cylwid", 0.75 }, + { "dashwid", 0.05 }, + { "dotrad", 0.015 }, + { "ellipseht", 0.5 }, + { "ellipsewid", 0.75 }, + { "fileht", 0.75 }, + { "filerad", 0.15 }, + { "filewid", 0.5 }, + { "fill", -1.0 }, + { "lineht", 0.5 }, + { "linewid", 0.5 }, + { "movewid", 0.5 }, + { "ovalht", 0.5 }, + { "ovalwid", 1.0 }, + { "scale", 1.0 }, + { "textht", 0.5 }, + { "textwid", 0.75 }, + { "thickness", 0.015 }, +}; + + +/* Methods for the "arc" class */ +static void arcInit(Pik *p, PObj *pObj){ + pObj->w = pik_value(p, "arcrad",6,0); + pObj->h = pObj->w; +} +/* Hack: Arcs are here rendered as quadratic Bezier curves rather +** than true arcs. Multiple reasons: (1) the legacy-PIC parameters +** that control arcs are obscure and I could not figure out what they +** mean based on available documentation. (2) Arcs are rarely used, +** and so do not seem that important. +*/ +static PPoint arcControlPoint(int cw, PPoint f, PPoint t, PNum rScale){ + PPoint m; + PNum dx, dy; + m.x = 0.5*(f.x+t.x); + m.y = 0.5*(f.y+t.y); + dx = t.x - f.x; + dy = t.y - f.y; + if( cw ){ + m.x -= 0.5*rScale*dy; + m.y += 0.5*rScale*dx; + }else{ + m.x += 0.5*rScale*dy; + m.y -= 0.5*rScale*dx; + } + return m; +} +static void arcCheck(Pik *p, PObj *pObj){ + PPoint m; + if( p->nTPath>2 ){ + pik_error(p, &pObj->errTok, "arc geometry error"); + return; + } + m = arcControlPoint(pObj->cw, p->aTPath[0], p->aTPath[1], 0.5); + pik_bbox_add_xy(&pObj->bbox, m.x, m.y); +} +static void arcRender(Pik *p, PObj *pObj){ + PPoint f, m, t; + if( pObj->nPath<2 ) return; + if( pObj->sw<=0.0 ) return; + f = pObj->aPath[0]; + t = pObj->aPath[1]; + m = arcControlPoint(pObj->cw,f,t,1.0); + if( pObj->larrow ){ + pik_draw_arrowhead(p,&m,&f,pObj); + } + if( pObj->rarrow ){ + pik_draw_arrowhead(p,&m,&t,pObj); + } + pik_append_xy(p,"\n", -1); + + pik_append_txt(p, pObj, 0); +} + + +/* Methods for the "arrow" class */ +static void arrowInit(Pik *p, PObj *pObj){ + pObj->w = pik_value(p, "linewid",7,0); + pObj->h = pik_value(p, "lineht",6,0); + pObj->rad = pik_value(p, "linerad",7,0); + pObj->rarrow = 1; +} + +/* Methods for the "box" class */ +static void boxInit(Pik *p, PObj *pObj){ + pObj->w = pik_value(p, "boxwid",6,0); + pObj->h = pik_value(p, "boxht",5,0); + pObj->rad = pik_value(p, "boxrad",6,0); +} +/* Return offset from the center of the box to the compass point +** given by parameter cp */ +static PPoint boxOffset(Pik *p, PObj *pObj, int cp){ + PPoint pt = cZeroPoint; + PNum w2 = 0.5*pObj->w; + PNum h2 = 0.5*pObj->h; + PNum rad = pObj->rad; + PNum rx; + if( rad<=0.0 ){ + rx = 0.0; + }else{ + if( rad>w2 ) rad = w2; + if( rad>h2 ) rad = h2; + rx = 0.29289321881345252392*rad; + } + switch( cp ){ + case CP_C: break; + case CP_N: pt.x = 0.0; pt.y = h2; break; + case CP_NE: pt.x = w2-rx; pt.y = h2-rx; break; + case CP_E: pt.x = w2; pt.y = 0.0; break; + case CP_SE: pt.x = w2-rx; pt.y = rx-h2; break; + case CP_S: pt.x = 0.0; pt.y = -h2; break; + case CP_SW: pt.x = rx-w2; pt.y = rx-h2; break; + case CP_W: pt.x = -w2; pt.y = 0.0; break; + case CP_NW: pt.x = rx-w2; pt.y = h2-rx; break; + default: assert(0); + } + UNUSED_PARAMETER(p); + return pt; +} +static PPoint boxChop(Pik *p, PObj *pObj, PPoint *pPt){ + PNum dx, dy; + int cp = CP_C; + PPoint chop = pObj->ptAt; + if( pObj->w<=0.0 ) return chop; + if( pObj->h<=0.0 ) return chop; + dx = (pPt->x - pObj->ptAt.x)*pObj->h/pObj->w; + dy = (pPt->y - pObj->ptAt.y); + if( dx>0.0 ){ + if( dy>=2.414*dx ){ + cp = CP_N; + }else if( dy>=0.414*dx ){ + cp = CP_NE; + }else if( dy>=-0.414*dx ){ + cp = CP_E; + }else if( dy>-2.414*dx ){ + cp = CP_SE; + }else{ + cp = CP_S; + } + }else{ + if( dy>=-2.414*dx ){ + cp = CP_N; + }else if( dy>=-0.414*dx ){ + cp = CP_NW; + }else if( dy>=0.414*dx ){ + cp = CP_W; + }else if( dy>2.414*dx ){ + cp = CP_SW; + }else{ + cp = CP_S; + } + } + chop = pObj->type->xOffset(p,pObj,cp); + chop.x += pObj->ptAt.x; + chop.y += pObj->ptAt.y; + return chop; +} +static void boxFit(Pik *p, PObj *pObj, PNum w, PNum h){ + if( w>0 ) pObj->w = w; + if( h>0 ) pObj->h = h; + UNUSED_PARAMETER(p); +} +static void boxRender(Pik *p, PObj *pObj){ + PNum w2 = 0.5*pObj->w; + PNum h2 = 0.5*pObj->h; + PNum rad = pObj->rad; + PPoint pt = pObj->ptAt; + if( pObj->sw>0.0 ){ + if( rad<=0.0 ){ + pik_append_xy(p,"w2 ) rad = w2; + if( rad>h2 ) rad = h2; + x0 = pt.x - w2; + x1 = x0 + rad; + x3 = pt.x + w2; + x2 = x3 - rad; + y0 = pt.y - h2; + y1 = y0 + rad; + y3 = pt.y + h2; + y2 = y3 - rad; + pik_append_xy(p,"x1 ) pik_append_xy(p, "L", x2, y0); + pik_append_arc(p, rad, rad, x3, y1); + if( y2>y1 ) pik_append_xy(p, "L", x3, y2); + pik_append_arc(p, rad, rad, x2, y3); + if( x2>x1 ) pik_append_xy(p, "L", x1, y3); + pik_append_arc(p, rad, rad, x0, y2); + if( y2>y1 ) pik_append_xy(p, "L", x0, y1); + pik_append_arc(p, rad, rad, x1, y0); + pik_append(p,"Z\" ",-1); + } + pik_append_style(p,pObj,3); + pik_append(p,"\" />\n", -1); + } + pik_append_txt(p, pObj, 0); +} + +/* Methods for the "circle" class */ +static void circleInit(Pik *p, PObj *pObj){ + pObj->w = pik_value(p, "circlerad",9,0)*2; + pObj->h = pObj->w; + pObj->rad = 0.5*pObj->w; +} +static void circleNumProp(Pik *p, PObj *pObj, PToken *pId){ + /* For a circle, the width must equal the height and both must + ** be twice the radius. Enforce those constraints. */ + switch( pId->eType ){ + case T_RADIUS: + pObj->w = pObj->h = 2.0*pObj->rad; + break; + case T_WIDTH: + pObj->h = pObj->w; + pObj->rad = 0.5*pObj->w; + break; + case T_HEIGHT: + pObj->w = pObj->h; + pObj->rad = 0.5*pObj->w; + break; + } + UNUSED_PARAMETER(p); +} +static PPoint circleChop(Pik *p, PObj *pObj, PPoint *pPt){ + PPoint chop; + PNum dx = pPt->x - pObj->ptAt.x; + PNum dy = pPt->y - pObj->ptAt.y; + PNum dist = hypot(dx,dy); + if( distrad || dist<=0 ) return pObj->ptAt; + chop.x = pObj->ptAt.x + dx*pObj->rad/dist; + chop.y = pObj->ptAt.y + dy*pObj->rad/dist; + UNUSED_PARAMETER(p); + return chop; +} +static void circleFit(Pik *p, PObj *pObj, PNum w, PNum h){ + PNum mx = 0.0; + if( w>0 ) mx = w; + if( h>mx ) mx = h; + if( w*h>0 && (w*w + h*h) > mx*mx ){ + mx = hypot(w,h); + } + if( mx>0.0 ){ + pObj->rad = 0.5*mx; + pObj->w = pObj->h = mx; + } + UNUSED_PARAMETER(p); +} + +static void circleRender(Pik *p, PObj *pObj){ + PNum r = pObj->rad; + PPoint pt = pObj->ptAt; + if( pObj->sw>0.0 ){ + pik_append_x(p,"\n", -1); + } + pik_append_txt(p, pObj, 0); +} + +/* Methods for the "cylinder" class */ +static void cylinderInit(Pik *p, PObj *pObj){ + pObj->w = pik_value(p, "cylwid",6,0); + pObj->h = pik_value(p, "cylht",5,0); + pObj->rad = pik_value(p, "cylrad",6,0); /* Minor radius of ellipses */ +} +static void cylinderFit(Pik *p, PObj *pObj, PNum w, PNum h){ + if( w>0 ) pObj->w = w; + if( h>0 ) pObj->h = h + 0.25*pObj->rad + pObj->sw; + UNUSED_PARAMETER(p); +} +static void cylinderRender(Pik *p, PObj *pObj){ + PNum w2 = 0.5*pObj->w; + PNum h2 = 0.5*pObj->h; + PNum rad = pObj->rad; + PPoint pt = pObj->ptAt; + if( pObj->sw>0.0 ){ + if( rad>h2 ){ + rad = h2; + }else if( rad<0 ){ + rad = 0; + } + pik_append_xy(p,"\n", -1); + } + pik_append_txt(p, pObj, 0); +} +static PPoint cylinderOffset(Pik *p, PObj *pObj, int cp){ + PPoint pt = cZeroPoint; + PNum w2 = pObj->w*0.5; + PNum h1 = pObj->h*0.5; + PNum h2 = h1 - pObj->rad; + switch( cp ){ + case CP_C: break; + case CP_N: pt.x = 0.0; pt.y = h1; break; + case CP_NE: pt.x = w2; pt.y = h2; break; + case CP_E: pt.x = w2; pt.y = 0.0; break; + case CP_SE: pt.x = w2; pt.y = -h2; break; + case CP_S: pt.x = 0.0; pt.y = -h1; break; + case CP_SW: pt.x = -w2; pt.y = -h2; break; + case CP_W: pt.x = -w2; pt.y = 0.0; break; + case CP_NW: pt.x = -w2; pt.y = h2; break; + default: assert(0); + } + UNUSED_PARAMETER(p); + return pt; +} + +/* Methods for the "dot" class */ +static void dotInit(Pik *p, PObj *pObj){ + pObj->rad = pik_value(p, "dotrad",6,0); + pObj->h = pObj->w = pObj->rad*6; + pObj->fill = pObj->color; +} +static void dotNumProp(Pik *p, PObj *pObj, PToken *pId){ + switch( pId->eType ){ + case T_COLOR: + pObj->fill = pObj->color; + break; + case T_FILL: + pObj->color = pObj->fill; + break; + } + UNUSED_PARAMETER(p); +} +static void dotCheck(Pik *p, PObj *pObj){ + pObj->w = pObj->h = 0; + pik_bbox_addellipse(&pObj->bbox, pObj->ptAt.x, pObj->ptAt.y, + pObj->rad, pObj->rad); + UNUSED_PARAMETER(p); +} +static PPoint dotOffset(Pik *p, PObj *pObj, int cp){ + UNUSED_PARAMETER(p); + UNUSED_PARAMETER(pObj); + UNUSED_PARAMETER(cp); + return cZeroPoint; +} +static void dotRender(Pik *p, PObj *pObj){ + PNum r = pObj->rad; + PPoint pt = pObj->ptAt; + if( pObj->sw>0.0 ){ + pik_append_x(p,"\n", -1); + } + pik_append_txt(p, pObj, 0); +} + + + +/* Methods for the "ellipse" class */ +static void ellipseInit(Pik *p, PObj *pObj){ + pObj->w = pik_value(p, "ellipsewid",10,0); + pObj->h = pik_value(p, "ellipseht",9,0); +} +static PPoint ellipseChop(Pik *p, PObj *pObj, PPoint *pPt){ + PPoint chop; + PNum s, dq, dist; + PNum dx = pPt->x - pObj->ptAt.x; + PNum dy = pPt->y - pObj->ptAt.y; + if( pObj->w<=0.0 ) return pObj->ptAt; + if( pObj->h<=0.0 ) return pObj->ptAt; + s = pObj->h/pObj->w; + dq = dx*s; + dist = hypot(dq,dy); + if( disth ) return pObj->ptAt; + chop.x = pObj->ptAt.x + 0.5*dq*pObj->h/(dist*s); + chop.y = pObj->ptAt.y + 0.5*dy*pObj->h/dist; + UNUSED_PARAMETER(p); + return chop; +} +static PPoint ellipseOffset(Pik *p, PObj *pObj, int cp){ + PPoint pt = cZeroPoint; + PNum w = pObj->w*0.5; + PNum w2 = w*0.70710678118654747608; + PNum h = pObj->h*0.5; + PNum h2 = h*0.70710678118654747608; + switch( cp ){ + case CP_C: break; + case CP_N: pt.x = 0.0; pt.y = h; break; + case CP_NE: pt.x = w2; pt.y = h2; break; + case CP_E: pt.x = w; pt.y = 0.0; break; + case CP_SE: pt.x = w2; pt.y = -h2; break; + case CP_S: pt.x = 0.0; pt.y = -h; break; + case CP_SW: pt.x = -w2; pt.y = -h2; break; + case CP_W: pt.x = -w; pt.y = 0.0; break; + case CP_NW: pt.x = -w2; pt.y = h2; break; + default: assert(0); + } + UNUSED_PARAMETER(p); + return pt; +} +static void ellipseRender(Pik *p, PObj *pObj){ + PNum w = pObj->w; + PNum h = pObj->h; + PPoint pt = pObj->ptAt; + if( pObj->sw>0.0 ){ + pik_append_x(p,"\n", -1); + } + pik_append_txt(p, pObj, 0); +} + +/* Methods for the "file" object */ +static void fileInit(Pik *p, PObj *pObj){ + pObj->w = pik_value(p, "filewid",7,0); + pObj->h = pik_value(p, "fileht",6,0); + pObj->rad = pik_value(p, "filerad",7,0); +} +/* Return offset from the center of the file to the compass point +** given by parameter cp */ +static PPoint fileOffset(Pik *p, PObj *pObj, int cp){ + PPoint pt = cZeroPoint; + PNum w2 = 0.5*pObj->w; + PNum h2 = 0.5*pObj->h; + PNum rx = pObj->rad; + PNum mn = w2

mn ) rx = mn; + if( rx0 ) pObj->w = w; + if( h>0 ) pObj->h = h + 2*pObj->rad; + UNUSED_PARAMETER(p); +} +static void fileRender(Pik *p, PObj *pObj){ + PNum w2 = 0.5*pObj->w; + PNum h2 = 0.5*pObj->h; + PNum rad = pObj->rad; + PPoint pt = pObj->ptAt; + PNum mn = w2

mn ) rad = mn; + if( radsw>0.0 ){ + pik_append_xy(p,"\n",-1); + pik_append_xy(p,"\n",-1); + } + pik_append_txt(p, pObj, 0); +} + + +/* Methods for the "line" class */ +static void lineInit(Pik *p, PObj *pObj){ + pObj->w = pik_value(p, "linewid",7,0); + pObj->h = pik_value(p, "lineht",6,0); + pObj->rad = pik_value(p, "linerad",7,0); +} +static PPoint lineOffset(Pik *p, PObj *pObj, int cp){ +#if 0 + /* In legacy PIC, the .center of an unclosed line is half way between + ** its .start and .end. */ + if( cp==CP_C && !pObj->bClose ){ + PPoint out; + out.x = 0.5*(pObj->ptEnter.x + pObj->ptExit.x) - pObj->ptAt.x; + out.y = 0.5*(pObj->ptEnter.x + pObj->ptExit.y) - pObj->ptAt.y; + return out; + } +#endif + return boxOffset(p,pObj,cp); +} +static void lineRender(Pik *p, PObj *pObj){ + int i; + if( pObj->sw>0.0 ){ + const char *z = "nPath; + if( pObj->larrow ){ + pik_draw_arrowhead(p,&pObj->aPath[1],&pObj->aPath[0],pObj); + } + if( pObj->rarrow ){ + pik_draw_arrowhead(p,&pObj->aPath[n-2],&pObj->aPath[n-1],pObj); + } + for(i=0; inPath; i++){ + pik_append_xy(p,z,pObj->aPath[i].x,pObj->aPath[i].y); + z = "L"; + } + if( pObj->bClose ){ + pik_append(p,"Z",1); + }else{ + pObj->fill = -1.0; + } + pik_append(p,"\" ",-1); + pik_append_style(p,pObj,pObj->bClose?3:0); + pik_append(p,"\" />\n", -1); + } + pik_append_txt(p, pObj, 0); +} + +/* Methods for the "move" class */ +static void moveInit(Pik *p, PObj *pObj){ + pObj->w = pik_value(p, "movewid",7,0); + pObj->h = pObj->w; + pObj->fill = -1.0; + pObj->color = -1.0; + pObj->sw = -1.0; +} +static void moveRender(Pik *p, PObj *pObj){ + /* No-op */ + UNUSED_PARAMETER(p); + UNUSED_PARAMETER(pObj); +} + +/* Methods for the "oval" class */ +static void ovalInit(Pik *p, PObj *pObj){ + pObj->h = pik_value(p, "ovalht",6,0); + pObj->w = pik_value(p, "ovalwid",7,0); + pObj->rad = 0.5*(pObj->hw?pObj->h:pObj->w); +} +static void ovalNumProp(Pik *p, PObj *pObj, PToken *pId){ + UNUSED_PARAMETER(p); + UNUSED_PARAMETER(pId); + /* Always adjust the radius to be half of the smaller of + ** the width and height. */ + pObj->rad = 0.5*(pObj->hw?pObj->h:pObj->w); +} +static void ovalFit(Pik *p, PObj *pObj, PNum w, PNum h){ + UNUSED_PARAMETER(p); + if( w>0 ) pObj->w = w; + if( h>0 ) pObj->h = h; + if( pObj->wh ) pObj->w = pObj->h; + pObj->rad = 0.5*(pObj->hw?pObj->h:pObj->w); +} + + + +/* Methods for the "spline" class */ +static void splineInit(Pik *p, PObj *pObj){ + pObj->w = pik_value(p, "linewid",7,0); + pObj->h = pik_value(p, "lineht",6,0); + pObj->rad = 1000; +} +/* Return a point along the path from "f" to "t" that is r units +** prior to reaching "t", except if the path is less than 2*r total, +** return the midpoint. +*/ +static PPoint radiusMidpoint(PPoint f, PPoint t, PNum r, int *pbMid){ + PNum dx = t.x - f.x; + PNum dy = t.y - f.y; + PNum dist = hypot(dx,dy); + PPoint m; + if( dist<=0.0 ) return t; + dx /= dist; + dy /= dist; + if( r > 0.5*dist ){ + r = 0.5*dist; + *pbMid = 1; + }else{ + *pbMid = 0; + } + m.x = t.x - r*dx; + m.y = t.y - r*dy; + return m; +} +static void radiusPath(Pik *p, PObj *pObj, PNum r){ + int i; + int n = pObj->nPath; + const PPoint *a = pObj->aPath; + PPoint m; + PPoint an = a[n-1]; + int isMid = 0; + int iLast = pObj->bClose ? n : n-1; + + pik_append_xy(p,"bClose ){ + pik_append(p,"Z",1); + }else{ + pObj->fill = -1.0; + } + pik_append(p,"\" ",-1); + pik_append_style(p,pObj,pObj->bClose?3:0); + pik_append(p,"\" />\n", -1); +} +static void splineRender(Pik *p, PObj *pObj){ + if( pObj->sw>0.0 ){ + int n = pObj->nPath; + PNum r = pObj->rad; + if( n<3 || r<=0.0 ){ + lineRender(p,pObj); + return; + } + if( pObj->larrow ){ + pik_draw_arrowhead(p,&pObj->aPath[1],&pObj->aPath[0],pObj); + } + if( pObj->rarrow ){ + pik_draw_arrowhead(p,&pObj->aPath[n-2],&pObj->aPath[n-1],pObj); + } + radiusPath(p,pObj,pObj->rad); + } + pik_append_txt(p, pObj, 0); +} + + +/* Methods for the "text" class */ +static void textInit(Pik *p, PObj *pObj){ + pik_value(p, "textwid",7,0); + pik_value(p, "textht",6,0); + pObj->sw = 0.0; +} +static PPoint textOffset(Pik *p, PObj *pObj, int cp){ + /* Automatically slim-down the width and height of text + ** statements so that the bounding box tightly encloses the text, + ** then get boxOffset() to do the offset computation. + */ + pik_size_to_fit(p, &pObj->errTok,3); + return boxOffset(p, pObj, cp); +} + +/* Methods for the "sublist" class */ +static void sublistInit(Pik *p, PObj *pObj){ + PList *pList = pObj->pSublist; + int i; + UNUSED_PARAMETER(p); + pik_bbox_init(&pObj->bbox); + for(i=0; in; i++){ + pik_bbox_addbox(&pObj->bbox, &pList->a[i]->bbox); + } + pObj->w = pObj->bbox.ne.x - pObj->bbox.sw.x; + pObj->h = pObj->bbox.ne.y - pObj->bbox.sw.y; + pObj->ptAt.x = 0.5*(pObj->bbox.ne.x + pObj->bbox.sw.x); + pObj->ptAt.y = 0.5*(pObj->bbox.ne.y + pObj->bbox.sw.y); + pObj->mCalc |= A_WIDTH|A_HEIGHT|A_RADIUS; +} + + +/* +** The following array holds all the different kinds of objects. +** The special [] object is separate. +*/ +static const PClass aClass[] = { + { /* name */ "arc", + /* isline */ 1, + /* eJust */ 0, + /* xInit */ arcInit, + /* xNumProp */ 0, + /* xCheck */ arcCheck, + /* xChop */ 0, + /* xOffset */ boxOffset, + /* xFit */ 0, + /* xRender */ arcRender + }, + { /* name */ "arrow", + /* isline */ 1, + /* eJust */ 0, + /* xInit */ arrowInit, + /* xNumProp */ 0, + /* xCheck */ 0, + /* xChop */ 0, + /* xOffset */ lineOffset, + /* xFit */ 0, + /* xRender */ splineRender + }, + { /* name */ "box", + /* isline */ 0, + /* eJust */ 1, + /* xInit */ boxInit, + /* xNumProp */ 0, + /* xCheck */ 0, + /* xChop */ boxChop, + /* xOffset */ boxOffset, + /* xFit */ boxFit, + /* xRender */ boxRender + }, + { /* name */ "circle", + /* isline */ 0, + /* eJust */ 0, + /* xInit */ circleInit, + /* xNumProp */ circleNumProp, + /* xCheck */ 0, + /* xChop */ circleChop, + /* xOffset */ ellipseOffset, + /* xFit */ circleFit, + /* xRender */ circleRender + }, + { /* name */ "cylinder", + /* isline */ 0, + /* eJust */ 1, + /* xInit */ cylinderInit, + /* xNumProp */ 0, + /* xCheck */ 0, + /* xChop */ boxChop, + /* xOffset */ cylinderOffset, + /* xFit */ cylinderFit, + /* xRender */ cylinderRender + }, + { /* name */ "dot", + /* isline */ 0, + /* eJust */ 0, + /* xInit */ dotInit, + /* xNumProp */ dotNumProp, + /* xCheck */ dotCheck, + /* xChop */ circleChop, + /* xOffset */ dotOffset, + /* xFit */ 0, + /* xRender */ dotRender + }, + { /* name */ "ellipse", + /* isline */ 0, + /* eJust */ 0, + /* xInit */ ellipseInit, + /* xNumProp */ 0, + /* xCheck */ 0, + /* xChop */ ellipseChop, + /* xOffset */ ellipseOffset, + /* xFit */ boxFit, + /* xRender */ ellipseRender + }, + { /* name */ "file", + /* isline */ 0, + /* eJust */ 1, + /* xInit */ fileInit, + /* xNumProp */ 0, + /* xCheck */ 0, + /* xChop */ boxChop, + /* xOffset */ fileOffset, + /* xFit */ fileFit, + /* xRender */ fileRender + }, + { /* name */ "line", + /* isline */ 1, + /* eJust */ 0, + /* xInit */ lineInit, + /* xNumProp */ 0, + /* xCheck */ 0, + /* xChop */ 0, + /* xOffset */ lineOffset, + /* xFit */ 0, + /* xRender */ splineRender + }, + { /* name */ "move", + /* isline */ 1, + /* eJust */ 0, + /* xInit */ moveInit, + /* xNumProp */ 0, + /* xCheck */ 0, + /* xChop */ 0, + /* xOffset */ boxOffset, + /* xFit */ 0, + /* xRender */ moveRender + }, + { /* name */ "oval", + /* isline */ 0, + /* eJust */ 1, + /* xInit */ ovalInit, + /* xNumProp */ ovalNumProp, + /* xCheck */ 0, + /* xChop */ boxChop, + /* xOffset */ boxOffset, + /* xFit */ ovalFit, + /* xRender */ boxRender + }, + { /* name */ "spline", + /* isline */ 1, + /* eJust */ 0, + /* xInit */ splineInit, + /* xNumProp */ 0, + /* xCheck */ 0, + /* xChop */ 0, + /* xOffset */ lineOffset, + /* xFit */ 0, + /* xRender */ splineRender + }, + { /* name */ "text", + /* isline */ 0, + /* eJust */ 0, + /* xInit */ textInit, + /* xNumProp */ 0, + /* xCheck */ 0, + /* xChop */ boxChop, + /* xOffset */ textOffset, + /* xFit */ boxFit, + /* xRender */ boxRender + }, +}; +static const PClass sublistClass = + { /* name */ "[]", + /* isline */ 0, + /* eJust */ 0, + /* xInit */ sublistInit, + /* xNumProp */ 0, + /* xCheck */ 0, + /* xChop */ 0, + /* xOffset */ boxOffset, + /* xFit */ 0, + /* xRender */ 0 + }; +static const PClass noopClass = + { /* name */ "noop", + /* isline */ 0, + /* eJust */ 0, + /* xInit */ 0, + /* xNumProp */ 0, + /* xCheck */ 0, + /* xChop */ 0, + /* xOffset */ boxOffset, + /* xFit */ 0, + /* xRender */ 0 + }; + + +/* +** Reduce the length of the line segment by amt (if possible) by +** modifying the location of *t. +*/ +static void pik_chop(PPoint *f, PPoint *t, PNum amt){ + PNum dx = t->x - f->x; + PNum dy = t->y - f->y; + PNum dist = hypot(dx,dy); + PNum r; + if( dist<=amt ){ + *t = *f; + return; + } + r = 1.0 - amt/dist; + t->x = f->x + r*dx; + t->y = f->y + r*dy; +} + +/* +** Draw an arrowhead on the end of the line segment from pFrom to pTo. +** Also, shorten the line segment (by changing the value of pTo) so that +** the shaft of the arrow does not extend into the arrowhead. +*/ +static void pik_draw_arrowhead(Pik *p, PPoint *f, PPoint *t, PObj *pObj){ + PNum dx = t->x - f->x; + PNum dy = t->y - f->y; + PNum dist = hypot(dx,dy); + PNum h = p->hArrow * pObj->sw; + PNum w = p->wArrow * pObj->sw; + PNum e1, ddx, ddy; + PNum bx, by; + if( pObj->color<0.0 ) return; + if( pObj->sw<=0.0 ) return; + if( dist<=0.0 ) return; /* Unable */ + dx /= dist; + dy /= dist; + e1 = dist - h; + if( e1<0.0 ){ + e1 = 0.0; + h = dist; + } + ddx = -w*dy; + ddy = w*dx; + bx = f->x + e1*dx; + by = f->y + e1*dy; + pik_append_xy(p,"x, t->y); + pik_append_xy(p," ",bx-ddx, by-ddy); + pik_append_xy(p," ",bx+ddx, by+ddy); + pik_append_clr(p,"\" style=\"fill:",pObj->color,"\"/>\n",0); + pik_chop(f,t,h/2); +} + +/* +** Compute the relative offset to an edge location from the reference for a +** an statement. +*/ +static PPoint pik_elem_offset(Pik *p, PObj *pObj, int cp){ + return pObj->type->xOffset(p, pObj, cp); +} + + +/* +** Append raw text to zOut +*/ +static void pik_append(Pik *p, const char *zText, int n){ + if( n<0 ) n = (int)strlen(zText); + if( p->nOut+n>=p->nOutAlloc ){ + int nNew = (p->nOut+n)*2 + 1; + char *z = realloc(p->zOut, nNew); + if( z==0 ){ + pik_error(p, 0, 0); + return; + } + p->zOut = z; + p->nOutAlloc = nNew; + } + memcpy(p->zOut+p->nOut, zText, n); + p->nOut += n; + p->zOut[p->nOut] = 0; +} + +/* +** Append text to zOut with HTML characters escaped. +** +** * The space character is changed into non-breaking space (U+00a0) +** if mFlags has the 0x01 bit set. This is needed when outputting +** text to preserve leading and trailing whitespace. Turns out we +** cannot use   as that is an HTML-ism and is not valid in XML. +** +** * The "&" character is changed into "&" if mFlags has the +** 0x02 bit set. This is needed when generating error message text. +** +** * Except for the above, only "<" and ">" are escaped. +*/ +static void pik_append_text(Pik *p, const char *zText, int n, int mFlags){ + int i; + char c = 0; + int bQSpace = mFlags & 1; + int bQAmp = mFlags & 2; + if( n<0 ) n = (int)strlen(zText); + while( n>0 ){ + for(i=0; i' ) break; + if( c==' ' && bQSpace ) break; + if( c=='&' && bQAmp ) break; + } + if( i ) pik_append(p, zText, i); + if( i==n ) break; + switch( c ){ + case '<': { pik_append(p, "<", 4); break; } + case '>': { pik_append(p, ">", 4); break; } + case '&': { pik_append(p, "&", 5); break; } + case ' ': { pik_append(p, "\302\240;", 2); break; } + } + i++; + n -= i; + zText += i; + i = 0; + } +} + +/* +** Append error message text. This is either a raw append, or an append +** with HTML escapes, depending on whether the PIKCHR_PLAINTEXT_ERRORS flag +** is set. +*/ +static void pik_append_errtxt(Pik *p, const char *zText, int n){ + if( p->mFlags & PIKCHR_PLAINTEXT_ERRORS ){ + pik_append(p, zText, n); + }else{ + pik_append_text(p, zText, n, 0); + } +} + +/* Append a PNum value +*/ +static void pik_append_num(Pik *p, const char *z,PNum v){ + char buf[100]; + snprintf(buf, sizeof(buf)-1, "%.10g", (double)v); + buf[sizeof(buf)-1] = 0; + pik_append(p, z, -1); + pik_append(p, buf, -1); +} + +/* Append a PPoint value (Used for debugging only) +*/ +static void pik_append_point(Pik *p, const char *z, PPoint *pPt){ + char buf[100]; + snprintf(buf, sizeof(buf)-1, "%.10g,%.10g", + (double)pPt->x, (double)pPt->y); + buf[sizeof(buf)-1] = 0; + pik_append(p, z, -1); + pik_append(p, buf, -1); +} + +/* +** Invert the RGB color so that it is appropriate for dark mode. +** Variable x hold the initial color. The color is intended for use +** as a background color if isBg is true, and as a foreground color +** if isBg is false. +*/ +static int pik_color_to_dark_mode(int x, int isBg){ + int r, g, b; + int mn, mx; + x = 0xffffff - x; + r = (x>>16) & 0xff; + g = (x>>8) & 0xff; + b = x & 0xff; + mx = r; + if( g>mx ) mx = g; + if( b>mx ) mx = b; + mn = r; + if( g127 ){ + r = (127*r)/mx; + g = (127*g)/mx; + b = (127*b)/mx; + } + }else{ + if( mn<128 && mx>mn ){ + r = 127 + ((r-mn)*128)/(mx-mn); + g = 127 + ((g-mn)*128)/(mx-mn); + b = 127 + ((b-mn)*128)/(mx-mn); + } + } + return r*0x10000 + g*0x100 + b; +} + +/* Append a PNum value surrounded by text. Do coordinate transformations +** on the value. +*/ +static void pik_append_x(Pik *p, const char *z1, PNum v, const char *z2){ + char buf[200]; + v -= p->bbox.sw.x; + snprintf(buf, sizeof(buf)-1, "%s%d%s", z1, pik_round(p->rScale*v), z2); + buf[sizeof(buf)-1] = 0; + pik_append(p, buf, -1); +} +static void pik_append_y(Pik *p, const char *z1, PNum v, const char *z2){ + char buf[200]; + v = p->bbox.ne.y - v; + snprintf(buf, sizeof(buf)-1, "%s%d%s", z1, pik_round(p->rScale*v), z2); + buf[sizeof(buf)-1] = 0; + pik_append(p, buf, -1); +} +static void pik_append_xy(Pik *p, const char *z1, PNum x, PNum y){ + char buf[200]; + x = x - p->bbox.sw.x; + y = p->bbox.ne.y - y; + snprintf(buf, sizeof(buf)-1, "%s%d,%d", z1, + pik_round(p->rScale*x), pik_round(p->rScale*y)); + buf[sizeof(buf)-1] = 0; + pik_append(p, buf, -1); +} +static void pik_append_dis(Pik *p, const char *z1, PNum v, const char *z2){ + char buf[200]; + snprintf(buf, sizeof(buf)-1, "%s%g%s", z1, p->rScale*v, z2); + buf[sizeof(buf)-1] = 0; + pik_append(p, buf, -1); +} + +/* Append a color specification to the output. +** +** In PIKCHR_DARK_MODE, the color is inverted. The "bg" flags indicates that +** the color is intended for use as a background color if true, or as a +** foreground color if false. The distinction only matters for color +** inversions in PIKCHR_DARK_MODE. +*/ +static void pik_append_clr(Pik *p,const char *z1,PNum v,const char *z2,int bg){ + char buf[200]; + int x = pik_round(v); + int r, g, b; + if( x==0 && p->fgcolor>0 && !bg ){ + x = p->fgcolor; + }else if( bg && x>=0xffffff && p->bgcolor>0 ){ + x = p->bgcolor; + }else if( p->mFlags & PIKCHR_DARK_MODE ){ + x = pik_color_to_dark_mode(x,bg); + } + r = (x>>16) & 0xff; + g = (x>>8) & 0xff; + b = x & 0xff; + snprintf(buf, sizeof(buf)-1, "%srgb(%d,%d,%d)%s", z1, r, g, b, z2); + buf[sizeof(buf)-1] = 0; + pik_append(p, buf, -1); +} + +/* Append an SVG path A record: +** +** A r1 r2 0 0 0 x y +*/ +static void pik_append_arc(Pik *p, PNum r1, PNum r2, PNum x, PNum y){ + char buf[200]; + x = x - p->bbox.sw.x; + y = p->bbox.ne.y - y; + snprintf(buf, sizeof(buf)-1, "A%d %d 0 0 0 %d %d", + pik_round(p->rScale*r1), pik_round(p->rScale*r2), + pik_round(p->rScale*x), pik_round(p->rScale*y)); + buf[sizeof(buf)-1] = 0; + pik_append(p, buf, -1); +} + +/* Append a style="..." text. But, leave the quote unterminated, in case +** the caller wants to add some more. +** +** eFill is non-zero to fill in the background, or 0 if no fill should +** occur. Non-zero values of eFill determine the "bg" flag to pik_append_clr() +** for cases when pObj->fill==pObj->color +** +** 1 fill is background, and color is foreground. +** 2 fill and color are both foreground. (Used by "dot" objects) +** 3 fill and color are both background. (Used by most other objs) +*/ +static void pik_append_style(Pik *p, PObj *pObj, int eFill){ + int clrIsBg = 0; + pik_append(p, " style=\"", -1); + if( pObj->fill>=0 && eFill ){ + int fillIsBg = 1; + if( pObj->fill==pObj->color ){ + if( eFill==2 ) fillIsBg = 0; + if( eFill==3 ) clrIsBg = 1; + } + pik_append_clr(p, "fill:", pObj->fill, ";", fillIsBg); + }else{ + pik_append(p,"fill:none;",-1); + } + if( pObj->sw>0.0 && pObj->color>=0.0 ){ + PNum sw = pObj->sw; + pik_append_dis(p, "stroke-width:", sw, ";"); + if( pObj->nPath>2 && pObj->rad<=pObj->sw ){ + pik_append(p, "stroke-linejoin:round;", -1); + } + pik_append_clr(p, "stroke:",pObj->color,";",clrIsBg); + if( pObj->dotted>0.0 ){ + PNum v = pObj->dotted; + if( sw<2.1/p->rScale ) sw = 2.1/p->rScale; + pik_append_dis(p,"stroke-dasharray:",sw,""); + pik_append_dis(p,",",v,";"); + }else if( pObj->dashed>0.0 ){ + PNum v = pObj->dashed; + pik_append_dis(p,"stroke-dasharray:",v,""); + pik_append_dis(p,",",v,";"); + } + } +} + +/* +** Compute the vertical locations for all text items in the +** object pObj. In other words, set every pObj->aTxt[*].eCode +** value to contain exactly one of: TP_ABOVE2, TP_ABOVE, TP_CENTER, +** TP_BELOW, or TP_BELOW2 is set. +*/ +static void pik_txt_vertical_layout(PObj *pObj){ + int n, i; + PToken *aTxt; + n = pObj->nTxt; + if( n==0 ) return; + aTxt = pObj->aTxt; + if( n==1 ){ + if( (aTxt[0].eCode & TP_VMASK)==0 ){ + aTxt[0].eCode |= TP_CENTER; + } + }else{ + int allSlots = 0; + int aFree[5]; + int iSlot; + int j, mJust; + /* If there is more than one TP_ABOVE, change the first to TP_ABOVE2. */ + for(j=mJust=0, i=n-1; i>=0; i--){ + if( aTxt[i].eCode & TP_ABOVE ){ + if( j==0 ){ + j++; + mJust = aTxt[i].eCode & TP_JMASK; + }else if( j==1 && mJust!=0 && (aTxt[i].eCode & mJust)==0 ){ + j++; + }else{ + aTxt[i].eCode = (aTxt[i].eCode & ~TP_VMASK) | TP_ABOVE2; + break; + } + } + } + /* If there is more than one TP_BELOW, change the last to TP_BELOW2 */ + for(j=mJust=0, i=0; i=4 && (allSlots & TP_ABOVE2)==0 ) aFree[iSlot++] = TP_ABOVE2; + if( (allSlots & TP_ABOVE)==0 ) aFree[iSlot++] = TP_ABOVE; + if( (n&1)!=0 ) aFree[iSlot++] = TP_CENTER; + if( (allSlots & TP_BELOW)==0 ) aFree[iSlot++] = TP_BELOW; + if( n>=4 && (allSlots & TP_BELOW2)==0 ) aFree[iSlot++] = TP_BELOW2; + } + /* Set the VMASK for all unassigned texts */ + for(i=iSlot=0; ieCode & TP_BIG ) scale *= 1.25; + if( t->eCode & TP_SMALL ) scale *= 0.8; + if( t->eCode & TP_XTRA ) scale *= scale; + return scale; +} + +/* Append multiple SVG elements for the text fields of the PObj. +** Parameters: +** +** p The Pik object into which we are rendering +** +** pObj Object containing the text to be rendered +** +** pBox If not NULL, do no rendering at all. Instead +** expand the box object so that it will include all +** of the text. +*/ +static void pik_append_txt(Pik *p, PObj *pObj, PBox *pBox){ + PNum jw; /* Justification margin relative to center */ + PNum ha2 = 0.0; /* Height of the top row of text */ + PNum ha1 = 0.0; /* Height of the second "above" row */ + PNum hc = 0.0; /* Height of the center row */ + PNum hb1 = 0.0; /* Height of the first "below" row of text */ + PNum hb2 = 0.0; /* Height of the second "below" row */ + PNum yBase = 0.0; + int n, i, nz; + PNum x, y, orig_y, s; + const char *z; + PToken *aTxt; + unsigned allMask = 0; + + if( p->nErr ) return; + if( pObj->nTxt==0 ) return; + aTxt = pObj->aTxt; + n = pObj->nTxt; + pik_txt_vertical_layout(pObj); + x = pObj->ptAt.x; + for(i=0; iaTxt[i].eCode; + if( pObj->type->isLine ){ + hc = pObj->sw*1.5; + }else if( pObj->rad>0.0 && pObj->type->xInit==cylinderInit ){ + yBase = -0.75*pObj->rad; + } + if( allMask & TP_CENTER ){ + for(i=0; iaTxt[i].eCode & TP_CENTER ){ + s = pik_font_scale(pObj->aTxt+i); + if( hccharHeight ) hc = s*p->charHeight; + } + } + } + if( allMask & TP_ABOVE ){ + for(i=0; iaTxt[i].eCode & TP_ABOVE ){ + s = pik_font_scale(pObj->aTxt+i)*p->charHeight; + if( ha1aTxt[i].eCode & TP_ABOVE2 ){ + s = pik_font_scale(pObj->aTxt+i)*p->charHeight; + if( ha2aTxt[i].eCode & TP_BELOW ){ + s = pik_font_scale(pObj->aTxt+i)*p->charHeight; + if( hb1aTxt[i].eCode & TP_BELOW2 ){ + s = pik_font_scale(pObj->aTxt+i)*p->charHeight; + if( hb2type->eJust==1 ){ + jw = 0.5*(pObj->w - 0.5*(p->charWidth + pObj->sw)); + }else{ + jw = 0.0; + } + for(i=0; iptAt.y; + y = yBase; + if( t->eCode & TP_ABOVE2 ) y += 0.5*hc + ha1 + 0.5*ha2; + if( t->eCode & TP_ABOVE ) y += 0.5*hc + 0.5*ha1; + if( t->eCode & TP_BELOW ) y -= 0.5*hc + 0.5*hb1; + if( t->eCode & TP_BELOW2 ) y -= 0.5*hc + hb1 + 0.5*hb2; + if( t->eCode & TP_LJUST ) nx -= jw; + if( t->eCode & TP_RJUST ) nx += jw; + + if( pBox!=0 ){ + /* If pBox is not NULL, do not draw any . Instead, just expand + ** pBox to include the text */ + PNum cw = pik_text_length(t)*p->charWidth*xtraFontScale*0.01; + PNum ch = p->charHeight*0.5*xtraFontScale; + PNum x0, y0, x1, y1; /* Boundary of text relative to pObj->ptAt */ + if( t->eCode & TP_BOLD ) cw *= 1.1; + if( t->eCode & TP_RJUST ){ + x0 = nx; + y0 = y-ch; + x1 = nx-cw; + y1 = y+ch; + }else if( t->eCode & TP_LJUST ){ + x0 = nx; + y0 = y-ch; + x1 = nx+cw; + y1 = y+ch; + }else{ + x0 = nx+cw/2; + y0 = y+ch; + x1 = nx-cw/2; + y1 = y-ch; + } + if( (t->eCode & TP_ALIGN)!=0 && pObj->nPath>=2 ){ + int nn = pObj->nPath; + PNum dx = pObj->aPath[nn-1].x - pObj->aPath[0].x; + PNum dy = pObj->aPath[nn-1].y - pObj->aPath[0].y; + if( dx!=0 || dy!=0 ){ + PNum dist = hypot(dx,dy); + PNum tt; + dx /= dist; + dy /= dist; + tt = dx*x0 - dy*y0; + y0 = dy*x0 - dx*y0; + x0 = tt; + tt = dx*x1 - dy*y1; + y1 = dy*x1 - dx*y1; + x1 = tt; + } + } + pik_bbox_add_xy(pBox, x+x0, orig_y+y0); + pik_bbox_add_xy(pBox, x+x1, orig_y+y1); + continue; + } + nx += x; + y += orig_y; + + pik_append_x(p, "eCode & TP_RJUST ){ + pik_append(p, " text-anchor=\"end\"", -1); + }else if( t->eCode & TP_LJUST ){ + pik_append(p, " text-anchor=\"start\"", -1); + }else{ + pik_append(p, " text-anchor=\"middle\"", -1); + } + if( t->eCode & TP_ITALIC ){ + pik_append(p, " font-style=\"italic\"", -1); + } + if( t->eCode & TP_BOLD ){ + pik_append(p, " font-weight=\"bold\"", -1); + } + if( pObj->color>=0.0 ){ + pik_append_clr(p, " fill=\"", pObj->color, "\"",0); + } + xtraFontScale *= p->fontScale; + if( xtraFontScale<=0.99 || xtraFontScale>=1.01 ){ + pik_append_num(p, " font-size=\"", xtraFontScale*100.0); + pik_append(p, "%\"", 2); + } + if( (t->eCode & TP_ALIGN)!=0 && pObj->nPath>=2 ){ + int nn = pObj->nPath; + PNum dx = pObj->aPath[nn-1].x - pObj->aPath[0].x; + PNum dy = pObj->aPath[nn-1].y - pObj->aPath[0].y; + if( dx!=0 || dy!=0 ){ + PNum ang = atan2(dy,dx)*-180/M_PI; + pik_append_num(p, " transform=\"rotate(", ang); + pik_append_xy(p, " ", x, orig_y); + pik_append(p,")\"",2); + } + } + pik_append(p," dominant-baseline=\"central\">",-1); + if( t->n>=2 && t->z[0]=='"' ){ + z = t->z+1; + nz = t->n-2; + }else{ + z = t->z; + nz = t->n; + } + while( nz>0 ){ + int j; + for(j=0; j\n", -1); + } +} + +/* +** Append text (that will go inside of a
...
) that +** shows the context of an error token. +*/ +static void pik_error_context(Pik *p, PToken *pErr, int nContext){ + int iErrPt; /* Index of first byte of error from start of input */ + int iErrCol; /* Column of the error token on its line */ + int iStart; /* Start position of the error context */ + int iEnd; /* End position of the error context */ + int iLineno; /* Line number of the error */ + int iFirstLineno; /* Line number of start of error context */ + int i; /* Loop counter */ + int iBump = 0; /* Bump the location of the error cursor */ + char zLineno[20]; /* Buffer in which to generate line numbers */ + + iErrPt = (int)(pErr->z - p->sIn.z); + if( iErrPt>=(int)p->sIn.n ){ + iErrPt = p->sIn.n-1; + iBump = 1; + }else{ + while( iErrPt>0 && (p->sIn.z[iErrPt]=='\n' || p->sIn.z[iErrPt]=='\r') ){ + iErrPt--; + iBump = 1; + } + } + iLineno = 1; + for(i=0; isIn.z[i]=='\n' ){ + iLineno++; + } + } + iStart = 0; + iFirstLineno = 1; + while( iFirstLineno+nContextsIn.z[iStart]!='\n' ){ iStart++; } + iStart++; + iFirstLineno++; + } + for(iEnd=iErrPt; p->sIn.z[iEnd]!=0 && p->sIn.z[iEnd]!='\n'; iEnd++){} + i = iStart; + while( iFirstLineno<=iLineno ){ + snprintf(zLineno,sizeof(zLineno)-1,"/* %4d */ ", iFirstLineno++); + zLineno[sizeof(zLineno)-1] = 0; + pik_append(p, zLineno, -1); + for(i=iStart; p->sIn.z[i]!=0 && p->sIn.z[i]!='\n'; i++){} + pik_append_errtxt(p, p->sIn.z+iStart, i-iStart); + iStart = i+1; + pik_append(p, "\n", 1); + } + for(iErrCol=0, i=iErrPt; i>0 && p->sIn.z[i]!='\n'; iErrCol++, i--){} + for(i=0; in; i++) pik_append(p, "^", 1); + pik_append(p, "\n", 1); +} + + +/* +** Generate an error message for the output. pErr is the token at which +** the error should point. zMsg is the text of the error message. If +** either pErr or zMsg is NULL, generate an out-of-memory error message. +** +** This routine is a no-op if there has already been an error reported. +*/ +static void pik_error(Pik *p, PToken *pErr, const char *zMsg){ + int i; + if( p==0 ) return; + if( p->nErr ) return; + p->nErr++; + if( zMsg==0 ){ + if( p->mFlags & PIKCHR_PLAINTEXT_ERRORS ){ + pik_append(p, "\nOut of memory\n", -1); + }else{ + pik_append(p, "\n

Out of memory

\n", -1); + } + return; + } + if( pErr==0 ){ + pik_append(p, "\n", 1); + pik_append_errtxt(p, zMsg, -1); + return; + } + if( (p->mFlags & PIKCHR_PLAINTEXT_ERRORS)==0 ){ + pik_append(p, "
\n", -1);
+  }
+  pik_error_context(p, pErr, 5);
+  pik_append(p, "ERROR: ", -1);
+  pik_append_errtxt(p, zMsg, -1);
+  pik_append(p, "\n", 1);
+  for(i=p->nCtx-1; i>=0; i--){
+    pik_append(p, "Called from:\n", -1);
+    pik_error_context(p, &p->aCtx[i], 0);
+  }
+  if( (p->mFlags & PIKCHR_PLAINTEXT_ERRORS)==0 ){
+    pik_append(p, "
\n", -1); + } +} + +/* +** Process an "assert( e1 == e2 )" statement. Always return NULL. +*/ +static PObj *pik_assert(Pik *p, PNum e1, PToken *pEq, PNum e2){ + char zE1[100], zE2[100], zMsg[300]; + + /* Convert the numbers to strings using %g for comparison. This + ** limits the precision of the comparison to account for rounding error. */ + snprintf(zE1, sizeof(zE1), "%g", e1); zE1[sizeof(zE1)-1] = 0; + snprintf(zE2, sizeof(zE2), "%g", e2); zE1[sizeof(zE2)-1] = 0; + if( strcmp(zE1,zE2)!=0 ){ + snprintf(zMsg, sizeof(zMsg), "%.50s != %.50s", zE1, zE2); + pik_error(p, pEq, zMsg); + } + return 0; +} + +/* +** Process an "assert( place1 == place2 )" statement. Always return NULL. +*/ +static PObj *pik_position_assert(Pik *p, PPoint *e1, PToken *pEq, PPoint *e2){ + char zE1[100], zE2[100], zMsg[210]; + + /* Convert the numbers to strings using %g for comparison. This + ** limits the precision of the comparison to account for rounding error. */ + snprintf(zE1, sizeof(zE1), "(%g,%g)", e1->x, e1->y); zE1[sizeof(zE1)-1] = 0; + snprintf(zE2, sizeof(zE2), "(%g,%g)", e2->x, e2->y); zE1[sizeof(zE2)-1] = 0; + if( strcmp(zE1,zE2)!=0 ){ + snprintf(zMsg, sizeof(zMsg), "%s != %s", zE1, zE2); + pik_error(p, pEq, zMsg); + } + return 0; +} + +/* Free a complete list of objects */ +static void pik_elist_free(Pik *p, PList *pList){ + int i; + if( pList==0 ) return; + for(i=0; in; i++){ + pik_elem_free(p, pList->a[i]); + } + free(pList->a); + free(pList); + return; +} + +/* Free a single object, and its substructure */ +static void pik_elem_free(Pik *p, PObj *pObj){ + if( pObj==0 ) return; + free(pObj->zName); + pik_elist_free(p, pObj->pSublist); + free(pObj->aPath); + free(pObj); +} + +/* Convert a numeric literal into a number. Return that number. +** There is no error handling because the tokenizer has already +** assured us that the numeric literal is valid. +** +** Allowed number forms: +** +** (1) Floating point literal +** (2) Same as (1) but followed by a unit: "cm", "mm", "in", +** "px", "pt", or "pc". +** (3) Hex integers: 0x000000 +** +** This routine returns the result in inches. If a different unit +** is specified, the conversion happens automatically. +*/ +PNum pik_atof(PToken *num){ + char *endptr; + PNum ans; + if( num->n>=3 && num->z[0]=='0' && (num->z[1]=='x'||num->z[1]=='X') ){ + return (PNum)strtol(num->z+2, 0, 16); + } + ans = strtod(num->z, &endptr); + if( (int)(endptr - num->z)==(int)num->n-2 ){ + char c1 = endptr[0]; + char c2 = endptr[1]; + if( c1=='c' && c2=='m' ){ + ans /= 2.54; + }else if( c1=='m' && c2=='m' ){ + ans /= 25.4; + }else if( c1=='p' && c2=='x' ){ + ans /= 96; + }else if( c1=='p' && c2=='t' ){ + ans /= 72; + }else if( c1=='p' && c2=='c' ){ + ans /= 6; + } + } + return ans; +} + +/* +** Compute the distance between two points +*/ +static PNum pik_dist(PPoint *pA, PPoint *pB){ + PNum dx, dy; + dx = pB->x - pA->x; + dy = pB->y - pA->y; + return hypot(dx,dy); +} + +/* Return true if a bounding box is empty. +*/ +static int pik_bbox_isempty(PBox *p){ + return p->sw.x>p->ne.x; +} + +/* Return true if point pPt is contained within the bounding box pBox +*/ +static int pik_bbox_contains_point(PBox *pBox, PPoint *pPt){ + if( pik_bbox_isempty(pBox) ) return 0; + if( pPt->x < pBox->sw.x ) return 0; + if( pPt->x > pBox->ne.x ) return 0; + if( pPt->y < pBox->sw.y ) return 0; + if( pPt->y > pBox->ne.y ) return 0; + return 1; +} + +/* Initialize a bounding box to an empty container +*/ +static void pik_bbox_init(PBox *p){ + p->sw.x = 1.0; + p->sw.y = 1.0; + p->ne.x = 0.0; + p->ne.y = 0.0; +} + +/* Enlarge the PBox of the first argument so that it fully +** covers the second PBox +*/ +static void pik_bbox_addbox(PBox *pA, PBox *pB){ + if( pik_bbox_isempty(pA) ){ + *pA = *pB; + } + if( pik_bbox_isempty(pB) ) return; + if( pA->sw.x>pB->sw.x ) pA->sw.x = pB->sw.x; + if( pA->sw.y>pB->sw.y ) pA->sw.y = pB->sw.y; + if( pA->ne.xne.x ) pA->ne.x = pB->ne.x; + if( pA->ne.yne.y ) pA->ne.y = pB->ne.y; +} + +/* Enlarge the PBox of the first argument, if necessary, so that +** it contains the point described by the 2nd and 3rd arguments. +*/ +static void pik_bbox_add_xy(PBox *pA, PNum x, PNum y){ + if( pik_bbox_isempty(pA) ){ + pA->ne.x = x; + pA->ne.y = y; + pA->sw.x = x; + pA->sw.y = y; + return; + } + if( pA->sw.x>x ) pA->sw.x = x; + if( pA->sw.y>y ) pA->sw.y = y; + if( pA->ne.xne.x = x; + if( pA->ne.yne.y = y; +} + +/* Enlarge the PBox so that it is able to contain an ellipse +** centered at x,y and with radiuses rx and ry. +*/ +static void pik_bbox_addellipse(PBox *pA, PNum x, PNum y, PNum rx, PNum ry){ + if( pik_bbox_isempty(pA) ){ + pA->ne.x = x+rx; + pA->ne.y = y+ry; + pA->sw.x = x-rx; + pA->sw.y = y-ry; + return; + } + if( pA->sw.x>x-rx ) pA->sw.x = x-rx; + if( pA->sw.y>y-ry ) pA->sw.y = y-ry; + if( pA->ne.xne.x = x+rx; + if( pA->ne.yne.y = y+ry; +} + + + +/* Append a new object onto the end of an object list. The +** object list is created if it does not already exist. Return +** the new object list. +*/ +static PList *pik_elist_append(Pik *p, PList *pList, PObj *pObj){ + if( pObj==0 ) return pList; + if( pList==0 ){ + pList = malloc(sizeof(*pList)); + if( pList==0 ){ + pik_error(p, 0, 0); + pik_elem_free(p, pObj); + return 0; + } + memset(pList, 0, sizeof(*pList)); + } + if( pList->n>=pList->nAlloc ){ + int nNew = (pList->n+5)*2; + PObj **pNew = realloc(pList->a, sizeof(PObj*)*nNew); + if( pNew==0 ){ + pik_error(p, 0, 0); + pik_elem_free(p, pObj); + return pList; + } + pList->nAlloc = nNew; + pList->a = pNew; + } + pList->a[pList->n++] = pObj; + p->list = pList; + return pList; +} + +/* Convert an object class name into a PClass pointer +*/ +static const PClass *pik_find_class(PToken *pId){ + int first = 0; + int last = count(aClass) - 1; + do{ + int mid = (first+last)/2; + int c = strncmp(aClass[mid].zName, pId->z, pId->n); + if( c==0 ){ + c = aClass[mid].zName[pId->n]!=0; + if( c==0 ) return &aClass[mid]; + } + if( c<0 ){ + first = mid + 1; + }else{ + last = mid - 1; + } + }while( first<=last ); + return 0; +} + +/* Allocate and return a new PObj object. +** +** If pId!=0 then pId is an identifier that defines the object class. +** If pStr!=0 then it is a STRING literal that defines a text object. +** If pSublist!=0 then this is a [...] object. If all three parameters +** are NULL then this is a no-op object used to define a PLACENAME. +*/ +static PObj *pik_elem_new(Pik *p, PToken *pId, PToken *pStr,PList *pSublist){ + PObj *pNew; + int miss = 0; + + if( p->nErr ) return 0; + pNew = malloc( sizeof(*pNew) ); + if( pNew==0 ){ + pik_error(p,0,0); + pik_elist_free(p, pSublist); + return 0; + } + memset(pNew, 0, sizeof(*pNew)); + p->cur = pNew; + p->nTPath = 1; + p->thenFlag = 0; + if( p->list==0 || p->list->n==0 ){ + pNew->ptAt.x = pNew->ptAt.y = 0.0; + pNew->eWith = CP_C; + }else{ + PObj *pPrior = p->list->a[p->list->n-1]; + pNew->ptAt = pPrior->ptExit; + switch( p->eDir ){ + default: pNew->eWith = CP_W; break; + case DIR_LEFT: pNew->eWith = CP_E; break; + case DIR_UP: pNew->eWith = CP_S; break; + case DIR_DOWN: pNew->eWith = CP_N; break; + } + } + p->aTPath[0] = pNew->ptAt; + pNew->with = pNew->ptAt; + pNew->outDir = pNew->inDir = p->eDir; + pNew->iLayer = pik_value_int(p, "layer", 5, &miss); + if( miss ) pNew->iLayer = 1000; + if( pNew->iLayer<0 ) pNew->iLayer = 0; + if( pSublist ){ + pNew->type = &sublistClass; + pNew->pSublist = pSublist; + sublistClass.xInit(p,pNew); + return pNew; + } + if( pStr ){ + PToken n; + n.z = "text"; + n.n = 4; + pNew->type = pik_find_class(&n); + assert( pNew->type!=0 ); + pNew->errTok = *pStr; + pNew->type->xInit(p, pNew); + pik_add_txt(p, pStr, pStr->eCode); + return pNew; + } + if( pId ){ + const PClass *pClass; + pNew->errTok = *pId; + pClass = pik_find_class(pId); + if( pClass ){ + pNew->type = pClass; + pNew->sw = pik_value(p, "thickness",9,0); + pNew->fill = pik_value(p, "fill",4,0); + pNew->color = pik_value(p, "color",5,0); + pClass->xInit(p, pNew); + return pNew; + } + pik_error(p, pId, "unknown object type"); + pik_elem_free(p, pNew); + return 0; + } + pNew->type = &noopClass; + pNew->ptExit = pNew->ptEnter = pNew->ptAt; + return pNew; +} + +/* +** If the ID token in the argument is the name of a macro, return +** the PMacro object for that macro +*/ +static PMacro *pik_find_macro(Pik *p, PToken *pId){ + PMacro *pMac; + for(pMac = p->pMacros; pMac; pMac=pMac->pNext){ + if( pMac->macroName.n==pId->n + && strncmp(pMac->macroName.z,pId->z,pId->n)==0 + ){ + return pMac; + } + } + return 0; +} + +/* Add a new macro +*/ +static void pik_add_macro( + Pik *p, /* Current Pikchr diagram */ + PToken *pId, /* The ID token that defines the macro name */ + PToken *pCode /* Macro body inside of {...} */ +){ + PMacro *pNew = pik_find_macro(p, pId); + if( pNew==0 ){ + pNew = malloc( sizeof(*pNew) ); + if( pNew==0 ){ + pik_error(p, 0, 0); + return; + } + pNew->pNext = p->pMacros; + p->pMacros = pNew; + pNew->macroName = *pId; + } + pNew->macroBody.z = pCode->z+1; + pNew->macroBody.n = pCode->n-2; + pNew->inUse = 0; +} + + +/* +** Set the output direction and exit point for an object +*/ +static void pik_elem_set_exit(PObj *pObj, int eDir){ + assert( ValidDir(eDir) ); + pObj->outDir = eDir; + if( !pObj->type->isLine || pObj->bClose ){ + pObj->ptExit = pObj->ptAt; + switch( pObj->outDir ){ + default: pObj->ptExit.x += pObj->w*0.5; break; + case DIR_LEFT: pObj->ptExit.x -= pObj->w*0.5; break; + case DIR_UP: pObj->ptExit.y += pObj->h*0.5; break; + case DIR_DOWN: pObj->ptExit.y -= pObj->h*0.5; break; + } + } +} + +/* Change the layout direction. +*/ +static void pik_set_direction(Pik *p, int eDir){ + assert( ValidDir(eDir) ); + p->eDir = (unsigned char)eDir; + + /* It seems to make sense to reach back into the last object and + ** change its exit point (its ".end") to correspond to the new + ** direction. Things just seem to work better this way. However, + ** legacy PIC does *not* do this. + ** + ** The difference can be seen in a script like this: + ** + ** arrow; circle; down; arrow + ** + ** You can make pikchr render the above exactly like PIC + ** by deleting the following three lines. But I (drh) think + ** it works better with those lines in place. + */ + if( p->list && p->list->n ){ + pik_elem_set_exit(p->list->a[p->list->n-1], eDir); + } +} + +/* Move all coordinates contained within an object (and within its +** substructure) by dx, dy +*/ +static void pik_elem_move(PObj *pObj, PNum dx, PNum dy){ + int i; + pObj->ptAt.x += dx; + pObj->ptAt.y += dy; + pObj->ptEnter.x += dx; + pObj->ptEnter.y += dy; + pObj->ptExit.x += dx; + pObj->ptExit.y += dy; + pObj->bbox.ne.x += dx; + pObj->bbox.ne.y += dy; + pObj->bbox.sw.x += dx; + pObj->bbox.sw.y += dy; + for(i=0; inPath; i++){ + pObj->aPath[i].x += dx; + pObj->aPath[i].y += dy; + } + if( pObj->pSublist ){ + pik_elist_move(pObj->pSublist, dx, dy); + } +} +static void pik_elist_move(PList *pList, PNum dx, PNum dy){ + int i; + for(i=0; in; i++){ + pik_elem_move(pList->a[i], dx, dy); + } +} + +/* +** Check to see if it is ok to set the value of paraemeter mThis. +** Return 0 if it is ok. If it not ok, generate an appropriate +** error message and return non-zero. +** +** Flags are set in pObj so that the same object or conflicting +** objects may not be set again. +** +** To be ok, bit mThis must be clear and no more than one of +** the bits identified by mBlockers may be set. +*/ +static int pik_param_ok( + Pik *p, /* For storing the error message (if any) */ + PObj *pObj, /* The object under construction */ + PToken *pId, /* Make the error point to this token */ + int mThis /* Value we are trying to set */ +){ + if( pObj->mProp & mThis ){ + pik_error(p, pId, "value is already set"); + return 1; + } + if( pObj->mCalc & mThis ){ + pik_error(p, pId, "value already fixed by prior constraints"); + return 1; + } + pObj->mProp |= mThis; + return 0; +} + + +/* +** Set a numeric property like "width 7" or "radius 200%". +** +** The rAbs term is an absolute value to add in. rRel is +** a relative value by which to change the current value. +*/ +void pik_set_numprop(Pik *p, PToken *pId, PRel *pVal){ + PObj *pObj = p->cur; + switch( pId->eType ){ + case T_HEIGHT: + if( pik_param_ok(p, pObj, pId, A_HEIGHT) ) return; + pObj->h = pObj->h*pVal->rRel + pVal->rAbs; + break; + case T_WIDTH: + if( pik_param_ok(p, pObj, pId, A_WIDTH) ) return; + pObj->w = pObj->w*pVal->rRel + pVal->rAbs; + break; + case T_RADIUS: + if( pik_param_ok(p, pObj, pId, A_RADIUS) ) return; + pObj->rad = pObj->rad*pVal->rRel + pVal->rAbs; + break; + case T_DIAMETER: + if( pik_param_ok(p, pObj, pId, A_RADIUS) ) return; + pObj->rad = pObj->rad*pVal->rRel + 0.5*pVal->rAbs; /* diam it 2x rad */ + break; + case T_THICKNESS: + if( pik_param_ok(p, pObj, pId, A_THICKNESS) ) return; + pObj->sw = pObj->sw*pVal->rRel + pVal->rAbs; + break; + } + if( pObj->type->xNumProp ){ + pObj->type->xNumProp(p, pObj, pId); + } + return; +} + +/* +** Set a color property. The argument is an RGB value. +*/ +void pik_set_clrprop(Pik *p, PToken *pId, PNum rClr){ + PObj *pObj = p->cur; + switch( pId->eType ){ + case T_FILL: + if( pik_param_ok(p, pObj, pId, A_FILL) ) return; + pObj->fill = rClr; + break; + case T_COLOR: + if( pik_param_ok(p, pObj, pId, A_COLOR) ) return; + pObj->color = rClr; + break; + } + if( pObj->type->xNumProp ){ + pObj->type->xNumProp(p, pObj, pId); + } + return; +} + +/* +** Set a "dashed" property like "dash 0.05" +** +** Use the value supplied by pVal if available. If pVal==0, use +** a default. +*/ +void pik_set_dashed(Pik *p, PToken *pId, PNum *pVal){ + PObj *pObj = p->cur; + PNum v; + switch( pId->eType ){ + case T_DOTTED: { + v = pVal==0 ? pik_value(p,"dashwid",7,0) : *pVal; + pObj->dotted = v; + pObj->dashed = 0.0; + break; + } + case T_DASHED: { + v = pVal==0 ? pik_value(p,"dashwid",7,0) : *pVal; + pObj->dashed = v; + pObj->dotted = 0.0; + break; + } + } +} + +/* +** If the current path information came from a "same" or "same as" +** reset it. +*/ +static void pik_reset_samepath(Pik *p){ + if( p->samePath ){ + p->samePath = 0; + p->nTPath = 1; + } +} + + +/* Add a new term to the path for a line-oriented object by transferring +** the information in the ptTo field over onto the path and into ptFrom +** resetting the ptTo. +*/ +static void pik_then(Pik *p, PToken *pToken, PObj *pObj){ + int n; + if( !pObj->type->isLine ){ + pik_error(p, pToken, "use with line-oriented objects only"); + return; + } + n = p->nTPath - 1; + if( n<1 && (pObj->mProp & A_FROM)==0 ){ + pik_error(p, pToken, "no prior path points"); + return; + } + p->thenFlag = 1; +} + +/* Advance to the next entry in p->aTPath. Return its index. +*/ +static int pik_next_rpath(Pik *p, PToken *pErr){ + int n = p->nTPath - 1; + if( n+1>=(int)count(p->aTPath) ){ + pik_error(0, pErr, "too many path elements"); + return n; + } + n++; + p->nTPath++; + p->aTPath[n] = p->aTPath[n-1]; + p->mTPath = 0; + return n; +} + +/* Add a direction term to an object. "up 0.5", or "left 3", or "down" +** or "down 50%". +*/ +static void pik_add_direction(Pik *p, PToken *pDir, PRel *pVal){ + PObj *pObj = p->cur; + int n; + int dir; + if( !pObj->type->isLine ){ + if( pDir ){ + pik_error(p, pDir, "use with line-oriented objects only"); + }else{ + PToken x = pik_next_semantic_token(&pObj->errTok); + pik_error(p, &x, "syntax error"); + } + return; + } + pik_reset_samepath(p); + n = p->nTPath - 1; + if( p->thenFlag || p->mTPath==3 || n==0 ){ + n = pik_next_rpath(p, pDir); + p->thenFlag = 0; + } + dir = pDir ? pDir->eCode : p->eDir; + switch( dir ){ + case DIR_UP: + if( p->mTPath & 2 ) n = pik_next_rpath(p, pDir); + p->aTPath[n].y += pVal->rAbs + pObj->h*pVal->rRel; + p->mTPath |= 2; + break; + case DIR_DOWN: + if( p->mTPath & 2 ) n = pik_next_rpath(p, pDir); + p->aTPath[n].y -= pVal->rAbs + pObj->h*pVal->rRel; + p->mTPath |= 2; + break; + case DIR_RIGHT: + if( p->mTPath & 1 ) n = pik_next_rpath(p, pDir); + p->aTPath[n].x += pVal->rAbs + pObj->w*pVal->rRel; + p->mTPath |= 1; + break; + case DIR_LEFT: + if( p->mTPath & 1 ) n = pik_next_rpath(p, pDir); + p->aTPath[n].x -= pVal->rAbs + pObj->w*pVal->rRel; + p->mTPath |= 1; + break; + } + pObj->outDir = dir; +} + +/* Process a movement attribute of one of these forms: +** +** pDist pHdgKW rHdg pEdgept +** GO distance HEADING angle +** GO distance compasspoint +*/ +static void pik_move_hdg( + Pik *p, /* The Pikchr context */ + PRel *pDist, /* Distance to move */ + PToken *pHeading, /* "heading" keyword if present */ + PNum rHdg, /* Angle argument to "heading" keyword */ + PToken *pEdgept, /* EDGEPT keyword "ne", "sw", etc... */ + PToken *pErr /* Token to use for error messages */ +){ + PObj *pObj = p->cur; + int n; + PNum rDist = pDist->rAbs + pik_value(p,"linewid",7,0)*pDist->rRel; + if( !pObj->type->isLine ){ + pik_error(p, pErr, "use with line-oriented objects only"); + return; + } + pik_reset_samepath(p); + do{ + n = pik_next_rpath(p, pErr); + }while( n<1 ); + if( pHeading ){ + if( rHdg<0.0 || rHdg>360.0 ){ + pik_error(p, pHeading, "headings should be between 0 and 360"); + return; + } + }else if( pEdgept->eEdge==CP_C ){ + pik_error(p, pEdgept, "syntax error"); + return; + }else{ + rHdg = pik_hdg_angle[pEdgept->eEdge]; + } + if( rHdg<=45.0 ){ + pObj->outDir = DIR_UP; + }else if( rHdg<=135.0 ){ + pObj->outDir = DIR_RIGHT; + }else if( rHdg<=225.0 ){ + pObj->outDir = DIR_DOWN; + }else if( rHdg<=315.0 ){ + pObj->outDir = DIR_LEFT; + }else{ + pObj->outDir = DIR_UP; + } + rHdg *= 0.017453292519943295769; /* degrees to radians */ + p->aTPath[n].x += rDist*sin(rHdg); + p->aTPath[n].y += rDist*cos(rHdg); + p->mTPath = 2; +} + + +/* Process a movement attribute of the form "right until even with ..." +** +** pDir is the first keyword, "right" or "left" or "up" or "down". +** The movement is in that direction until its closest approach to +** the point specified by pPoint. +*/ +static void pik_evenwith(Pik *p, PToken *pDir, PPoint *pPlace){ + PObj *pObj = p->cur; + int n; + if( !pObj->type->isLine ){ + pik_error(p, pDir, "use with line-oriented objects only"); + return; + } + pik_reset_samepath(p); + n = p->nTPath - 1; + if( p->thenFlag || p->mTPath==3 || n==0 ){ + n = pik_next_rpath(p, pDir); + p->thenFlag = 0; + } + switch( pDir->eCode ){ + case DIR_DOWN: + case DIR_UP: + if( p->mTPath & 2 ) n = pik_next_rpath(p, pDir); + p->aTPath[n].y = pPlace->y; + p->mTPath |= 2; + break; + case DIR_RIGHT: + case DIR_LEFT: + if( p->mTPath & 1 ) n = pik_next_rpath(p, pDir); + p->aTPath[n].x = pPlace->x; + p->mTPath |= 1; + break; + } + pObj->outDir = pDir->eCode; +} + +/* If the last referenced object is centered at point pPt then return +** a pointer to that object. If there is no prior object reference, +** or if the points are not the same, return NULL. +** +** This is a side-channel hack used to find the objects at which a +** line begins and ends. For example, in +** +** arrow from OBJ1 to OBJ2 chop +** +** The arrow object is normally just handed the coordinates of the +** centers for OBJ1 and OBJ2. But we also want to know the specific +** object named in case there are multiple objects centered at the +** same point. +** +** See forum post 1d46e3a0bc +*/ +static PObj *pik_last_ref_object(Pik *p, PPoint *pPt){ + PObj *pRes = 0; + if( p->lastRef==0 ) return 0; + if( p->lastRef->ptAt.x==pPt->x + && p->lastRef->ptAt.y==pPt->y + ){ + pRes = p->lastRef; + } + p->lastRef = 0; + return pRes; +} + +/* Set the "from" of an object +*/ +static void pik_set_from(Pik *p, PObj *pObj, PToken *pTk, PPoint *pPt){ + if( !pObj->type->isLine ){ + pik_error(p, pTk, "use \"at\" to position this object"); + return; + } + if( pObj->mProp & A_FROM ){ + pik_error(p, pTk, "line start location already fixed"); + return; + } + if( pObj->bClose ){ + pik_error(p, pTk, "polygon is closed"); + return; + } + if( p->nTPath>1 ){ + PNum dx = pPt->x - p->aTPath[0].x; + PNum dy = pPt->y - p->aTPath[0].y; + int i; + for(i=1; inTPath; i++){ + p->aTPath[i].x += dx; + p->aTPath[i].y += dy; + } + } + p->aTPath[0] = *pPt; + p->mTPath = 3; + pObj->mProp |= A_FROM; + pObj->pFrom = pik_last_ref_object(p, pPt); +} + +/* Set the "to" of an object +*/ +static void pik_add_to(Pik *p, PObj *pObj, PToken *pTk, PPoint *pPt){ + int n = p->nTPath-1; + if( !pObj->type->isLine ){ + pik_error(p, pTk, "use \"at\" to position this object"); + return; + } + if( pObj->bClose ){ + pik_error(p, pTk, "polygon is closed"); + return; + } + pik_reset_samepath(p); + if( n==0 || p->mTPath==3 || p->thenFlag ){ + n = pik_next_rpath(p, pTk); + } + p->aTPath[n] = *pPt; + p->mTPath = 3; + pObj->pTo = pik_last_ref_object(p, pPt); +} + +static void pik_close_path(Pik *p, PToken *pErr){ + PObj *pObj = p->cur; + if( p->nTPath<3 ){ + pik_error(p, pErr, + "need at least 3 vertexes in order to close the polygon"); + return; + } + if( pObj->bClose ){ + pik_error(p, pErr, "polygon already closed"); + return; + } + pObj->bClose = 1; +} + +/* Lower the layer of the current object so that it is behind the +** given object. +*/ +static void pik_behind(Pik *p, PObj *pOther){ + PObj *pObj = p->cur; + if( p->nErr==0 && pObj->iLayer>=pOther->iLayer ){ + pObj->iLayer = pOther->iLayer - 1; + } +} + + +/* Set the "at" of an object +*/ +static void pik_set_at(Pik *p, PToken *pEdge, PPoint *pAt, PToken *pErrTok){ + PObj *pObj; + static unsigned char eDirToCp[] = { CP_E, CP_S, CP_W, CP_N }; + if( p->nErr ) return; + pObj = p->cur; + + if( pObj->type->isLine ){ + pik_error(p, pErrTok, "use \"from\" and \"to\" to position this object"); + return; + } + if( pObj->mProp & A_AT ){ + pik_error(p, pErrTok, "location fixed by prior \"at\""); + return; + } + pObj->mProp |= A_AT; + pObj->eWith = pEdge ? pEdge->eEdge : CP_C; + if( pObj->eWith>=CP_END ){ + int dir = pObj->eWith==CP_END ? pObj->outDir : pObj->inDir; + pObj->eWith = eDirToCp[dir]; + } + pObj->with = *pAt; +} + +/* +** Try to add a text attribute to an object +*/ +static void pik_add_txt(Pik *p, PToken *pTxt, int iPos){ + PObj *pObj = p->cur; + PToken *pT; + if( pObj->nTxt >= count(pObj->aTxt) ){ + pik_error(p, pTxt, "too many text terms"); + return; + } + pT = &pObj->aTxt[pObj->nTxt++]; + *pT = *pTxt; + pT->eCode = (short)iPos; +} + +/* Merge "text-position" flags +*/ +static int pik_text_position(int iPrev, PToken *pFlag){ + int iRes = iPrev; + switch( pFlag->eType ){ + case T_LJUST: iRes = (iRes&~TP_JMASK) | TP_LJUST; break; + case T_RJUST: iRes = (iRes&~TP_JMASK) | TP_RJUST; break; + case T_ABOVE: iRes = (iRes&~TP_VMASK) | TP_ABOVE; break; + case T_CENTER: iRes = (iRes&~TP_VMASK) | TP_CENTER; break; + case T_BELOW: iRes = (iRes&~TP_VMASK) | TP_BELOW; break; + case T_ITALIC: iRes |= TP_ITALIC; break; + case T_BOLD: iRes |= TP_BOLD; break; + case T_ALIGNED: iRes |= TP_ALIGN; break; + case T_BIG: if( iRes & TP_BIG ) iRes |= TP_XTRA; + else iRes = (iRes &~TP_SZMASK)|TP_BIG; break; + case T_SMALL: if( iRes & TP_SMALL ) iRes |= TP_XTRA; + else iRes = (iRes &~TP_SZMASK)|TP_SMALL; break; + } + return iRes; +} + +/* +** Table of scale-factor estimates for variable-width characters. +** Actual character widths vary by font. These numbers are only +** guesses. And this table only provides data for ASCII. +** +** 100 means normal width. +*/ +static const unsigned char awChar[] = { + /* Skip initial 32 control characters */ + /* ' ' */ 45, + /* '!' */ 55, + /* '"' */ 62, + /* '#' */ 115, + /* '$' */ 90, + /* '%' */ 132, + /* '&' */ 125, + /* '\''*/ 40, + + /* '(' */ 55, + /* ')' */ 55, + /* '*' */ 71, + /* '+' */ 115, + /* ',' */ 45, + /* '-' */ 48, + /* '.' */ 45, + /* '/' */ 50, + + /* '0' */ 91, + /* '1' */ 91, + /* '2' */ 91, + /* '3' */ 91, + /* '4' */ 91, + /* '5' */ 91, + /* '6' */ 91, + /* '7' */ 91, + + /* '8' */ 91, + /* '9' */ 91, + /* ':' */ 50, + /* ';' */ 50, + /* '<' */ 120, + /* '=' */ 120, + /* '>' */ 120, + /* '?' */ 78, + + /* '@' */ 142, + /* 'A' */ 102, + /* 'B' */ 105, + /* 'C' */ 110, + /* 'D' */ 115, + /* 'E' */ 105, + /* 'F' */ 98, + /* 'G' */ 105, + + /* 'H' */ 125, + /* 'I' */ 58, + /* 'J' */ 58, + /* 'K' */ 107, + /* 'L' */ 95, + /* 'M' */ 145, + /* 'N' */ 125, + /* 'O' */ 115, + + /* 'P' */ 95, + /* 'Q' */ 115, + /* 'R' */ 107, + /* 'S' */ 95, + /* 'T' */ 97, + /* 'U' */ 118, + /* 'V' */ 102, + /* 'W' */ 150, + + /* 'X' */ 100, + /* 'Y' */ 93, + /* 'Z' */ 100, + /* '[' */ 58, + /* '\\'*/ 50, + /* ']' */ 58, + /* '^' */ 119, + /* '_' */ 72, + + /* '`' */ 72, + /* 'a' */ 86, + /* 'b' */ 92, + /* 'c' */ 80, + /* 'd' */ 92, + /* 'e' */ 85, + /* 'f' */ 52, + /* 'g' */ 92, + + /* 'h' */ 92, + /* 'i' */ 47, + /* 'j' */ 47, + /* 'k' */ 88, + /* 'l' */ 48, + /* 'm' */ 135, + /* 'n' */ 92, + /* 'o' */ 86, + + /* 'p' */ 92, + /* 'q' */ 92, + /* 'r' */ 69, + /* 's' */ 75, + /* 't' */ 58, + /* 'u' */ 92, + /* 'v' */ 80, + /* 'w' */ 121, + + /* 'x' */ 81, + /* 'y' */ 80, + /* 'z' */ 76, + /* '{' */ 91, + /* '|'*/ 49, + /* '}' */ 91, + /* '~' */ 118, +}; + +/* Return an estimate of the width of the displayed characters +** in a character string. The returned value is 100 times the +** average character width. +** +** Omit "\" used to escape characters. And count entities like +** "<" as a single character. Multi-byte UTF8 characters count +** as a single character. +** +** Attempt to scale the answer by the actual characters seen. Wide +** characters count more than narrow characters. But the widths are +** only guesses. +*/ +static int pik_text_length(const PToken *pToken){ + int n = pToken->n; + const char *z = pToken->z; + int cnt, j; + for(j=1, cnt=0; j=0x20 && c<=0x7e ){ + cnt += awChar[c-0x20]; + }else{ + cnt += 100; + } + } + return cnt; +} + +/* Adjust the width, height, and/or radius of the object so that +** it fits around the text that has been added so far. +** +** (1) Only text specified prior to this attribute is considered. +** (2) The text size is estimated based on the charht and charwid +** variable settings. +** (3) The fitted attributes can be changed again after this +** attribute, for example using "width 110%" if this auto-fit +** underestimates the text size. +** (4) Previously set attributes will not be altered. In other words, +** "width 1in fit" might cause the height to change, but the +** width is now set. +** (5) This only works for attributes that have an xFit method. +** +** The eWhich parameter is: +** +** 1: Fit horizontally only +** 2: Fit vertically only +** 3: Fit both ways +*/ +static void pik_size_to_fit(Pik *p, PToken *pFit, int eWhich){ + PObj *pObj; + PNum w, h; + PBox bbox; + if( p->nErr ) return; + pObj = p->cur; + + if( pObj->nTxt==0 ){ + pik_error(0, pFit, "no text to fit to"); + return; + } + if( pObj->type->xFit==0 ) return; + pik_bbox_init(&bbox); + pik_compute_layout_settings(p); + pik_append_txt(p, pObj, &bbox); + w = (eWhich & 1)!=0 ? (bbox.ne.x - bbox.sw.x) + p->charWidth : 0; + if( eWhich & 2 ){ + PNum h1, h2; + h1 = (bbox.ne.y - pObj->ptAt.y); + h2 = (pObj->ptAt.y - bbox.sw.y); + h = 2.0*( h1

charHeight; + }else{ + h = 0; + } + pObj->type->xFit(p, pObj, w, h); + pObj->mProp |= A_FIT; +} + +/* Set a local variable name to "val". +** +** The name might be a built-in variable or a color name. In either case, +** a new application-defined variable is set. Since app-defined variables +** are searched first, this will override any built-in variables. +*/ +static void pik_set_var(Pik *p, PToken *pId, PNum val, PToken *pOp){ + PVar *pVar = p->pVar; + while( pVar ){ + if( pik_token_eq(pId,pVar->zName)==0 ) break; + pVar = pVar->pNext; + } + if( pVar==0 ){ + char *z; + pVar = malloc( pId->n+1 + sizeof(*pVar) ); + if( pVar==0 ){ + pik_error(p, 0, 0); + return; + } + pVar->zName = z = (char*)&pVar[1]; + memcpy(z, pId->z, pId->n); + z[pId->n] = 0; + pVar->pNext = p->pVar; + pVar->val = pik_value(p, pId->z, pId->n, 0); + p->pVar = pVar; + } + switch( pOp->eCode ){ + case T_PLUS: pVar->val += val; break; + case T_STAR: pVar->val *= val; break; + case T_MINUS: pVar->val -= val; break; + case T_SLASH: + if( val==0.0 ){ + pik_error(p, pOp, "division by zero"); + }else{ + pVar->val /= val; + } + break; + default: pVar->val = val; break; + } + p->bLayoutVars = 0; /* Clear the layout setting cache */ +} + +/* +** Round a PNum into the nearest integer +*/ +static int pik_round(PNum v){ + if( isnan(v) ) return 0; + if( v < -2147483647 ) return (-2147483647-1); + if( v >= 2147483647 ) return 2147483647; + return (int)v; +} + +/* +** Search for the variable named z[0..n-1] in: +** +** * Application defined variables +** * Built-in variables +** +** Return the value of the variable if found. If not found +** return 0.0. Also if pMiss is not NULL, then set it to 1 +** if not found. +** +** This routine is a subroutine to pik_get_var(). But it is also +** used by object implementations to look up (possibly overwritten) +** values for built-in variables like "boxwid". +*/ +static PNum pik_value(Pik *p, const char *z, int n, int *pMiss){ + PVar *pVar; + int first, last, mid, c; + for(pVar=p->pVar; pVar; pVar=pVar->pNext){ + if( strncmp(pVar->zName,z,n)==0 && pVar->zName[n]==0 ){ + return pVar->val; + } + } + first = 0; + last = count(aBuiltin)-1; + while( first<=last ){ + mid = (first+last)/2; + c = strncmp(z,aBuiltin[mid].zName,n); + if( c==0 && aBuiltin[mid].zName[n] ) c = 1; + if( c==0 ) return aBuiltin[mid].val; + if( c>0 ){ + first = mid+1; + }else{ + last = mid-1; + } + } + if( pMiss ) *pMiss = 1; + return 0.0; +} +static int pik_value_int(Pik *p, const char *z, int n, int *pMiss){ + return pik_round(pik_value(p,z,n,pMiss)); +} + +/* +** Look up a color-name. Unlike other names in this program, the +** color-names are not case sensitive. So "DarkBlue" and "darkblue" +** and "DARKBLUE" all find the same value (139). +** +** If not found, return -99.0. Also post an error if p!=NULL. +** +** Special color names "None" and "Off" return -1.0 without causing +** an error. +*/ +static PNum pik_lookup_color(Pik *p, PToken *pId){ + int first, last, mid, c = 0; + first = 0; + last = count(aColor)-1; + while( first<=last ){ + const char *zClr; + int c1, c2; + unsigned int i; + mid = (first+last)/2; + zClr = aColor[mid].zName; + for(i=0; in; i++){ + c1 = zClr[i]&0x7f; + if( isupper(c1) ) c1 = tolower(c1); + c2 = pId->z[i]&0x7f; + if( isupper(c2) ) c2 = tolower(c2); + c = c2 - c1; + if( c ) break; + } + if( c==0 && aColor[mid].zName[pId->n] ) c = -1; + if( c==0 ) return (double)aColor[mid].val; + if( c>0 ){ + first = mid+1; + }else{ + last = mid-1; + } + } + if( p ) pik_error(p, pId, "not a known color name"); + return -99.0; +} + +/* Get the value of a variable. +** +** Search in order: +** +** * Application defined variables +** * Built-in variables +** * Color names +** +** If no such variable is found, throw an error. +*/ +static PNum pik_get_var(Pik *p, PToken *pId){ + int miss = 0; + PNum v = pik_value(p, pId->z, pId->n, &miss); + if( miss==0 ) return v; + v = pik_lookup_color(0, pId); + if( v>-90.0 ) return v; + pik_error(p,pId,"no such variable"); + return 0.0; +} + +/* Convert a T_NTH token (ex: "2nd", "5th"} into a numeric value and +** return that value. Throw an error if the value is too big. +*/ +static short int pik_nth_value(Pik *p, PToken *pNth){ + int i = atoi(pNth->z); + if( i>1000 ){ + pik_error(p, pNth, "value too big - max '1000th'"); + i = 1; + } + if( i==0 && pik_token_eq(pNth,"first")==0 ) i = 1; + return (short int)i; +} + +/* Search for the NTH object. +** +** If pBasis is not NULL then it should be a [] object. Use the +** sublist of that [] object for the search. If pBasis is not a [] +** object, then throw an error. +** +** The pNth token describes the N-th search. The pNth->eCode value +** is one more than the number of items to skip. It is negative +** to search backwards. If pNth->eType==T_ID, then it is the name +** of a class to search for. If pNth->eType==T_LB, then +** search for a [] object. If pNth->eType==T_LAST, then search for +** any type. +** +** Raise an error if the item is not found. +*/ +static PObj *pik_find_nth(Pik *p, PObj *pBasis, PToken *pNth){ + PList *pList; + int i, n; + const PClass *pClass; + if( pBasis==0 ){ + pList = p->list; + }else{ + pList = pBasis->pSublist; + } + if( pList==0 ){ + pik_error(p, pNth, "no such object"); + return 0; + } + if( pNth->eType==T_LAST ){ + pClass = 0; + }else if( pNth->eType==T_LB ){ + pClass = &sublistClass; + }else{ + pClass = pik_find_class(pNth); + if( pClass==0 ){ + pik_error(0, pNth, "no such object type"); + return 0; + } + } + n = pNth->eCode; + if( n<0 ){ + for(i=pList->n-1; i>=0; i--){ + PObj *pObj = pList->a[i]; + if( pClass && pObj->type!=pClass ) continue; + n++; + if( n==0 ){ return pObj; } + } + }else{ + for(i=0; in; i++){ + PObj *pObj = pList->a[i]; + if( pClass && pObj->type!=pClass ) continue; + n--; + if( n==0 ){ return pObj; } + } + } + pik_error(p, pNth, "no such object"); + return 0; +} + +/* Search for an object by name. +** +** Search in pBasis->pSublist if pBasis is not NULL. If pBasis is NULL +** then search in p->list. +*/ +static PObj *pik_find_byname(Pik *p, PObj *pBasis, PToken *pName){ + PList *pList; + int i, j; + if( pBasis==0 ){ + pList = p->list; + }else{ + pList = pBasis->pSublist; + } + if( pList==0 ){ + pik_error(p, pName, "no such object"); + return 0; + } + /* First look explicitly tagged objects */ + for(i=pList->n-1; i>=0; i--){ + PObj *pObj = pList->a[i]; + if( pObj->zName && pik_token_eq(pName,pObj->zName)==0 ){ + p->lastRef = pObj; + return pObj; + } + } + /* If not found, do a second pass looking for any object containing + ** text which exactly matches pName */ + for(i=pList->n-1; i>=0; i--){ + PObj *pObj = pList->a[i]; + for(j=0; jnTxt; j++){ + if( pObj->aTxt[j].n==pName->n+2 + && memcmp(pObj->aTxt[j].z+1,pName->z,pName->n)==0 ){ + p->lastRef = pObj; + return pObj; + } + } + } + pik_error(p, pName, "no such object"); + return 0; +} + +/* Change most of the settings for the current object to be the +** same as the pOther object, or the most recent object of the same +** type if pOther is NULL. +*/ +static void pik_same(Pik *p, PObj *pOther, PToken *pErrTok){ + PObj *pObj = p->cur; + if( p->nErr ) return; + if( pOther==0 ){ + int i; + for(i=(p->list ? p->list->n : 0)-1; i>=0; i--){ + pOther = p->list->a[i]; + if( pOther->type==pObj->type ) break; + } + if( i<0 ){ + pik_error(p, pErrTok, "no prior objects of the same type"); + return; + } + } + if( pOther->nPath && pObj->type->isLine ){ + PNum dx, dy; + int i; + dx = p->aTPath[0].x - pOther->aPath[0].x; + dy = p->aTPath[0].y - pOther->aPath[0].y; + for(i=1; inPath; i++){ + p->aTPath[i].x = pOther->aPath[i].x + dx; + p->aTPath[i].y = pOther->aPath[i].y + dy; + } + p->nTPath = pOther->nPath; + p->mTPath = 3; + p->samePath = 1; + } + if( !pObj->type->isLine ){ + pObj->w = pOther->w; + pObj->h = pOther->h; + } + pObj->rad = pOther->rad; + pObj->sw = pOther->sw; + pObj->dashed = pOther->dashed; + pObj->dotted = pOther->dotted; + pObj->fill = pOther->fill; + pObj->color = pOther->color; + pObj->cw = pOther->cw; + pObj->larrow = pOther->larrow; + pObj->rarrow = pOther->rarrow; + pObj->bClose = pOther->bClose; + pObj->bChop = pOther->bChop; + pObj->inDir = pOther->inDir; + pObj->outDir = pOther->outDir; + pObj->iLayer = pOther->iLayer; +} + + +/* Return a "Place" associated with object pObj. If pEdge is NULL +** return the center of the object. Otherwise, return the corner +** described by pEdge. +*/ +static PPoint pik_place_of_elem(Pik *p, PObj *pObj, PToken *pEdge){ + PPoint pt = cZeroPoint; + const PClass *pClass; + if( pObj==0 ) return pt; + if( pEdge==0 ){ + return pObj->ptAt; + } + pClass = pObj->type; + if( pEdge->eType==T_EDGEPT || (pEdge->eEdge>0 && pEdge->eEdgexOffset(p, pObj, pEdge->eEdge); + pt.x += pObj->ptAt.x; + pt.y += pObj->ptAt.y; + return pt; + } + if( pEdge->eType==T_START ){ + return pObj->ptEnter; + }else{ + return pObj->ptExit; + } +} + +/* Do a linear interpolation of two positions. +*/ +static PPoint pik_position_between(PNum x, PPoint p1, PPoint p2){ + PPoint out; + out.x = p2.x*x + p1.x*(1.0 - x); + out.y = p2.y*x + p1.y*(1.0 - x); + return out; +} + +/* Compute the position that is dist away from pt at an heading angle of r +** +** The angle is a compass heading in degrees. North is 0 (or 360). +** East is 90. South is 180. West is 270. And so forth. +*/ +static PPoint pik_position_at_angle(PNum dist, PNum r, PPoint pt){ + r *= 0.017453292519943295769; /* degrees to radians */ + pt.x += dist*sin(r); + pt.y += dist*cos(r); + return pt; +} + +/* Compute the position that is dist away at a compass point +*/ +static PPoint pik_position_at_hdg(PNum dist, PToken *pD, PPoint pt){ + return pik_position_at_angle(dist, pik_hdg_angle[pD->eEdge], pt); +} + +/* Return the coordinates for the n-th vertex of a line. +*/ +static PPoint pik_nth_vertex(Pik *p, PToken *pNth, PToken *pErr, PObj *pObj){ + static const PPoint zero = {0, 0}; + int n; + if( p->nErr || pObj==0 ) return p->aTPath[0]; + if( !pObj->type->isLine ){ + pik_error(p, pErr, "object is not a line"); + return zero; + } + n = atoi(pNth->z); + if( n<1 || n>pObj->nPath ){ + pik_error(p, pNth, "no such vertex"); + return zero; + } + return pObj->aPath[n-1]; +} + +/* Return the value of a property of an object. +*/ +static PNum pik_property_of(PObj *pObj, PToken *pProp){ + PNum v = 0.0; + if( pObj ){ + switch( pProp->eType ){ + case T_HEIGHT: v = pObj->h; break; + case T_WIDTH: v = pObj->w; break; + case T_RADIUS: v = pObj->rad; break; + case T_DIAMETER: v = pObj->rad*2.0; break; + case T_THICKNESS: v = pObj->sw; break; + case T_DASHED: v = pObj->dashed; break; + case T_DOTTED: v = pObj->dotted; break; + case T_FILL: v = pObj->fill; break; + case T_COLOR: v = pObj->color; break; + case T_X: v = pObj->ptAt.x; break; + case T_Y: v = pObj->ptAt.y; break; + case T_TOP: v = pObj->bbox.ne.y; break; + case T_BOTTOM: v = pObj->bbox.sw.y; break; + case T_LEFT: v = pObj->bbox.sw.x; break; + case T_RIGHT: v = pObj->bbox.ne.x; break; + } + } + return v; +} + +/* Compute one of the built-in functions +*/ +static PNum pik_func(Pik *p, PToken *pFunc, PNum x, PNum y){ + PNum v = 0.0; + switch( pFunc->eCode ){ + case FN_ABS: v = v<0.0 ? -v : v; break; + case FN_COS: v = cos(x); break; + case FN_INT: v = rint(x); break; + case FN_SIN: v = sin(x); break; + case FN_SQRT: + if( x<0.0 ){ + pik_error(p, pFunc, "sqrt of negative value"); + v = 0.0; + }else{ + v = sqrt(x); + } + break; + case FN_MAX: v = x>y ? x : y; break; + case FN_MIN: v = xzName); + pObj->zName = malloc(pName->n+1); + if( pObj->zName==0 ){ + pik_error(p,0,0); + }else{ + memcpy(pObj->zName,pName->z,pName->n); + pObj->zName[pName->n] = 0; + } + return; +} + +/* +** Search for object located at *pCenter that has an xChop method and +** that does not enclose point pOther. +** +** Return a pointer to the object, or NULL if not found. +*/ +static PObj *pik_find_chopper(PList *pList, PPoint *pCenter, PPoint *pOther){ + int i; + if( pList==0 ) return 0; + for(i=pList->n-1; i>=0; i--){ + PObj *pObj = pList->a[i]; + if( pObj->type->xChop!=0 + && pObj->ptAt.x==pCenter->x + && pObj->ptAt.y==pCenter->y + && !pik_bbox_contains_point(&pObj->bbox, pOther) + ){ + return pObj; + }else if( pObj->pSublist ){ + pObj = pik_find_chopper(pObj->pSublist,pCenter,pOther); + if( pObj ) return pObj; + } + } + return 0; +} + +/* +** There is a line traveling from pFrom to pTo. +** +** If pObj is not null and is a choppable object, then chop at +** the boundary of pObj - where the line crosses the boundary +** of pObj. +** +** If pObj is NULL or has no xChop method, then search for some +** other object centered at pTo that is choppable and use it +** instead. +*/ +static void pik_autochop(Pik *p, PPoint *pFrom, PPoint *pTo, PObj *pObj){ + if( pObj==0 || pObj->type->xChop==0 ){ + pObj = pik_find_chopper(p->list, pTo, pFrom); + } + if( pObj ){ + *pTo = pObj->type->xChop(p, pObj, pFrom); + } +} + +/* This routine runs after all attributes have been received +** on an object. +*/ +static void pik_after_adding_attributes(Pik *p, PObj *pObj){ + int i; + PPoint ofst; + PNum dx, dy; + + if( p->nErr ) return; + + /* Position block objects */ + if( pObj->type->isLine==0 ){ + /* A height or width less than or equal to zero means "autofit". + ** Change the height or width to be big enough to contain the text, + */ + if( pObj->h<=0.0 ){ + if( pObj->nTxt==0 ){ + pObj->h = 0.0; + }else if( pObj->w<=0.0 ){ + pik_size_to_fit(p, &pObj->errTok, 3); + }else{ + pik_size_to_fit(p, &pObj->errTok, 2); + } + } + if( pObj->w<=0.0 ){ + if( pObj->nTxt==0 ){ + pObj->w = 0.0; + }else{ + pik_size_to_fit(p, &pObj->errTok, 1); + } + } + ofst = pik_elem_offset(p, pObj, pObj->eWith); + dx = (pObj->with.x - ofst.x) - pObj->ptAt.x; + dy = (pObj->with.y - ofst.y) - pObj->ptAt.y; + if( dx!=0 || dy!=0 ){ + pik_elem_move(pObj, dx, dy); + } + } + + /* For a line object with no movement specified, a single movement + ** of the default length in the current direction + */ + if( pObj->type->isLine && p->nTPath<2 ){ + pik_next_rpath(p, 0); + assert( p->nTPath==2 ); + switch( pObj->inDir ){ + default: p->aTPath[1].x += pObj->w; break; + case DIR_DOWN: p->aTPath[1].y -= pObj->h; break; + case DIR_LEFT: p->aTPath[1].x -= pObj->w; break; + case DIR_UP: p->aTPath[1].y += pObj->h; break; + } + if( pObj->type->xInit==arcInit ){ + pObj->outDir = (pObj->inDir + (pObj->cw ? 1 : 3))%4; + p->eDir = (unsigned char)pObj->outDir; + switch( pObj->outDir ){ + default: p->aTPath[1].x += pObj->w; break; + case DIR_DOWN: p->aTPath[1].y -= pObj->h; break; + case DIR_LEFT: p->aTPath[1].x -= pObj->w; break; + case DIR_UP: p->aTPath[1].y += pObj->h; break; + } + } + } + + /* Initialize the bounding box prior to running xCheck */ + pik_bbox_init(&pObj->bbox); + + /* Run object-specific code */ + if( pObj->type->xCheck!=0 ){ + pObj->type->xCheck(p,pObj); + if( p->nErr ) return; + } + + /* Compute final bounding box, entry and exit points, center + ** point (ptAt) and path for the object + */ + if( pObj->type->isLine ){ + pObj->aPath = malloc( sizeof(PPoint)*p->nTPath ); + if( pObj->aPath==0 ){ + pik_error(p, 0, 0); + return; + }else{ + pObj->nPath = p->nTPath; + for(i=0; inTPath; i++){ + pObj->aPath[i] = p->aTPath[i]; + } + } + + /* "chop" processing: + ** If the line goes to the center of an object with an + ** xChop method, then use the xChop method to trim the line. + */ + if( pObj->bChop && pObj->nPath>=2 ){ + int n = pObj->nPath; + pik_autochop(p, &pObj->aPath[n-2], &pObj->aPath[n-1], pObj->pTo); + pik_autochop(p, &pObj->aPath[1], &pObj->aPath[0], pObj->pFrom); + } + + pObj->ptEnter = pObj->aPath[0]; + pObj->ptExit = pObj->aPath[pObj->nPath-1]; + + /* Compute the center of the line based on the bounding box over + ** the vertexes. This is a difference from PIC. In Pikchr, the + ** center of a line is the center of its bounding box. In PIC, the + ** center of a line is halfway between its .start and .end. For + ** straight lines, this is the same point, but for multi-segment + ** lines the result is usually diferent */ + for(i=0; inPath; i++){ + pik_bbox_add_xy(&pObj->bbox, pObj->aPath[i].x, pObj->aPath[i].y); + } + pObj->ptAt.x = (pObj->bbox.ne.x + pObj->bbox.sw.x)/2.0; + pObj->ptAt.y = (pObj->bbox.ne.y + pObj->bbox.sw.y)/2.0; + + /* Reset the width and height of the object to be the width and height + ** of the bounding box over vertexes */ + pObj->w = pObj->bbox.ne.x - pObj->bbox.sw.x; + pObj->h = pObj->bbox.ne.y - pObj->bbox.sw.y; + + /* If this is a polygon (if it has the "close" attribute), then + ** adjust the exit point */ + if( pObj->bClose ){ + /* For "closed" lines, the .end is one of the .e, .s, .w, or .n + ** points of the bounding box, as with block objects. */ + pik_elem_set_exit(pObj, pObj->inDir); + } + }else{ + PNum w2 = pObj->w/2.0; + PNum h2 = pObj->h/2.0; + pObj->ptEnter = pObj->ptAt; + pObj->ptExit = pObj->ptAt; + switch( pObj->inDir ){ + default: pObj->ptEnter.x -= w2; break; + case DIR_LEFT: pObj->ptEnter.x += w2; break; + case DIR_UP: pObj->ptEnter.y -= h2; break; + case DIR_DOWN: pObj->ptEnter.y += h2; break; + } + switch( pObj->outDir ){ + default: pObj->ptExit.x += w2; break; + case DIR_LEFT: pObj->ptExit.x -= w2; break; + case DIR_UP: pObj->ptExit.y += h2; break; + case DIR_DOWN: pObj->ptExit.y -= h2; break; + } + pik_bbox_add_xy(&pObj->bbox, pObj->ptAt.x - w2, pObj->ptAt.y - h2); + pik_bbox_add_xy(&pObj->bbox, pObj->ptAt.x + w2, pObj->ptAt.y + h2); + } + p->eDir = (unsigned char)pObj->outDir; +} + +/* Show basic information about each object as a comment in the +** generated HTML. Used for testing and debugging. Activated +** by the (undocumented) "debug = 1;" +** command. +*/ +static void pik_elem_render(Pik *p, PObj *pObj){ + char *zDir; + if( pObj==0 ) return; + pik_append(p,"\n", -1); +} + +/* Render a list of objects +*/ +void pik_elist_render(Pik *p, PList *pList){ + int i; + int iNextLayer = 0; + int iThisLayer; + int bMoreToDo; + int miss = 0; + int mDebug = pik_value_int(p, "debug", 5, 0); + PNum colorLabel; + do{ + bMoreToDo = 0; + iThisLayer = iNextLayer; + iNextLayer = 0x7fffffff; + for(i=0; in; i++){ + PObj *pObj = pList->a[i]; + void (*xRender)(Pik*,PObj*); + if( pObj->iLayer>iThisLayer ){ + if( pObj->iLayeriLayer; + bMoreToDo = 1; + continue; /* Defer until another round */ + }else if( pObj->iLayertype->xRender; + if( xRender ){ + xRender(p, pObj); + } + if( pObj->pSublist ){ + pik_elist_render(p, pObj->pSublist); + } + } + }while( bMoreToDo ); + + /* If the color_debug_label value is defined, then go through + ** and paint a dot at every label location */ + colorLabel = pik_value(p, "debug_label_color", 17, &miss); + if( miss==0 && colorLabel>=0.0 ){ + PObj dot; + memset(&dot, 0, sizeof(dot)); + dot.type = &noopClass; + dot.rad = 0.015; + dot.sw = 0.015; + dot.fill = colorLabel; + dot.color = colorLabel; + dot.nTxt = 1; + dot.aTxt[0].eCode = TP_ABOVE; + for(i=0; in; i++){ + PObj *pObj = pList->a[i]; + if( pObj->zName==0 ) continue; + dot.ptAt = pObj->ptAt; + dot.aTxt[0].z = pObj->zName; + dot.aTxt[0].n = (int)strlen(pObj->zName); + dotRender(p, &dot); + } + } +} + +/* Add all objects of the list pList to the bounding box +*/ +static void pik_bbox_add_elist(Pik *p, PList *pList, PNum wArrow){ + int i; + for(i=0; in; i++){ + PObj *pObj = pList->a[i]; + if( pObj->sw>0.0 ) pik_bbox_addbox(&p->bbox, &pObj->bbox); + pik_append_txt(p, pObj, &p->bbox); + if( pObj->pSublist ) pik_bbox_add_elist(p, pObj->pSublist, wArrow); + + + /* Expand the bounding box to account for arrowheads on lines */ + if( pObj->type->isLine && pObj->nPath>0 ){ + if( pObj->larrow ){ + pik_bbox_addellipse(&p->bbox, pObj->aPath[0].x, pObj->aPath[0].y, + wArrow, wArrow); + } + if( pObj->rarrow ){ + int j = pObj->nPath-1; + pik_bbox_addellipse(&p->bbox, pObj->aPath[j].x, pObj->aPath[j].y, + wArrow, wArrow); + } + } + } +} + +/* Recompute key layout parameters from variables. */ +static void pik_compute_layout_settings(Pik *p){ + PNum thickness; /* Line thickness */ + PNum wArrow; /* Width of arrowheads */ + + /* Set up rendering parameters */ + if( p->bLayoutVars ) return; + thickness = pik_value(p,"thickness",9,0); + if( thickness<=0.01 ) thickness = 0.01; + wArrow = 0.5*pik_value(p,"arrowwid",8,0); + p->wArrow = wArrow/thickness; + p->hArrow = pik_value(p,"arrowht",7,0)/thickness; + p->fontScale = pik_value(p,"fontscale",9,0); + if( p->fontScale<=0.0 ) p->fontScale = 1.0; + p->rScale = 144.0; + p->charWidth = pik_value(p,"charwid",7,0)*p->fontScale; + p->charHeight = pik_value(p,"charht",6,0)*p->fontScale; + p->bLayoutVars = 1; +} + +/* Render a list of objects. Write the SVG into p->zOut. +** Delete the input object_list before returnning. +*/ +static void pik_render(Pik *p, PList *pList){ + if( pList==0 ) return; + if( p->nErr==0 ){ + PNum thickness; /* Stroke width */ + PNum margin; /* Extra bounding box margin */ + PNum w, h; /* Drawing width and height */ + PNum wArrow; + PNum pikScale; /* Value of the "scale" variable */ + int miss = 0; + + /* Set up rendering parameters */ + pik_compute_layout_settings(p); + thickness = pik_value(p,"thickness",9,0); + if( thickness<=0.01 ) thickness = 0.01; + margin = pik_value(p,"margin",6,0); + margin += thickness; + wArrow = p->wArrow*thickness; + miss = 0; + p->fgcolor = pik_value_int(p,"fgcolor",7,&miss); + if( miss ){ + PToken t; + t.z = "fgcolor"; + t.n = 7; + p->fgcolor = pik_round(pik_lookup_color(0, &t)); + } + miss = 0; + p->bgcolor = pik_value_int(p,"bgcolor",7,&miss); + if( miss ){ + PToken t; + t.z = "bgcolor"; + t.n = 7; + p->bgcolor = pik_round(pik_lookup_color(0, &t)); + } + + /* Compute a bounding box over all objects so that we can know + ** how big to declare the SVG canvas */ + pik_bbox_init(&p->bbox); + pik_bbox_add_elist(p, pList, wArrow); + + /* Expand the bounding box slightly to account for line thickness + ** and the optional "margin = EXPR" setting. */ + p->bbox.ne.x += margin + pik_value(p,"rightmargin",11,0); + p->bbox.ne.y += margin + pik_value(p,"topmargin",9,0); + p->bbox.sw.x -= margin + pik_value(p,"leftmargin",10,0); + p->bbox.sw.y -= margin + pik_value(p,"bottommargin",12,0); + + /* Output the SVG */ + pik_append(p, "zClass ){ + pik_append(p, " class=\"", -1); + pik_append(p, p->zClass, -1); + pik_append(p, "\"", 1); + } + w = p->bbox.ne.x - p->bbox.sw.x; + h = p->bbox.ne.y - p->bbox.sw.y; + p->wSVG = pik_round(p->rScale*w); + p->hSVG = pik_round(p->rScale*h); + pikScale = pik_value(p,"scale",5,0); + if( pikScale>=0.001 && pikScale<=1000.0 + && (pikScale<0.99 || pikScale>1.01) + ){ + p->wSVG = pik_round(p->wSVG*pikScale); + p->hSVG = pik_round(p->hSVG*pikScale); + pik_append_num(p, " width=\"", p->wSVG); + pik_append_num(p, "\" height=\"", p->hSVG); + pik_append(p, "\"", 1); + } + pik_append_dis(p, " viewBox=\"0 0 ",w,""); + pik_append_dis(p, " ",h,"\">\n"); + pik_elist_render(p, pList); + pik_append(p,"\n", -1); + }else{ + p->wSVG = -1; + p->hSVG = -1; + } + pik_elist_free(p, pList); +} + + + +/* +** An array of this structure defines a list of keywords. +*/ +typedef struct PikWord { + char *zWord; /* Text of the keyword */ + unsigned char nChar; /* Length of keyword text in bytes */ + unsigned char eType; /* Token code */ + unsigned char eCode; /* Extra code for the token */ + unsigned char eEdge; /* CP_* code for corner/edge keywords */ +} PikWord; + +/* +** Keywords +*/ +static const PikWord pik_keywords[] = { + { "above", 5, T_ABOVE, 0, 0 }, + { "abs", 3, T_FUNC1, FN_ABS, 0 }, + { "aligned", 7, T_ALIGNED, 0, 0 }, + { "and", 3, T_AND, 0, 0 }, + { "as", 2, T_AS, 0, 0 }, + { "assert", 6, T_ASSERT, 0, 0 }, + { "at", 2, T_AT, 0, 0 }, + { "behind", 6, T_BEHIND, 0, 0 }, + { "below", 5, T_BELOW, 0, 0 }, + { "between", 7, T_BETWEEN, 0, 0 }, + { "big", 3, T_BIG, 0, 0 }, + { "bold", 4, T_BOLD, 0, 0 }, + { "bot", 3, T_EDGEPT, 0, CP_S }, + { "bottom", 6, T_BOTTOM, 0, CP_S }, + { "c", 1, T_EDGEPT, 0, CP_C }, + { "ccw", 3, T_CCW, 0, 0 }, + { "center", 6, T_CENTER, 0, CP_C }, + { "chop", 4, T_CHOP, 0, 0 }, + { "close", 5, T_CLOSE, 0, 0 }, + { "color", 5, T_COLOR, 0, 0 }, + { "cos", 3, T_FUNC1, FN_COS, 0 }, + { "cw", 2, T_CW, 0, 0 }, + { "dashed", 6, T_DASHED, 0, 0 }, + { "define", 6, T_DEFINE, 0, 0 }, + { "diameter", 8, T_DIAMETER, 0, 0 }, + { "dist", 4, T_DIST, 0, 0 }, + { "dotted", 6, T_DOTTED, 0, 0 }, + { "down", 4, T_DOWN, DIR_DOWN, 0 }, + { "e", 1, T_EDGEPT, 0, CP_E }, + { "east", 4, T_EDGEPT, 0, CP_E }, + { "end", 3, T_END, 0, CP_END }, + { "even", 4, T_EVEN, 0, 0 }, + { "fill", 4, T_FILL, 0, 0 }, + { "first", 5, T_NTH, 0, 0 }, + { "fit", 3, T_FIT, 0, 0 }, + { "from", 4, T_FROM, 0, 0 }, + { "go", 2, T_GO, 0, 0 }, + { "heading", 7, T_HEADING, 0, 0 }, + { "height", 6, T_HEIGHT, 0, 0 }, + { "ht", 2, T_HEIGHT, 0, 0 }, + { "in", 2, T_IN, 0, 0 }, + { "int", 3, T_FUNC1, FN_INT, 0 }, + { "invis", 5, T_INVIS, 0, 0 }, + { "invisible", 9, T_INVIS, 0, 0 }, + { "italic", 6, T_ITALIC, 0, 0 }, + { "last", 4, T_LAST, 0, 0 }, + { "left", 4, T_LEFT, DIR_LEFT, CP_W }, + { "ljust", 5, T_LJUST, 0, 0 }, + { "max", 3, T_FUNC2, FN_MAX, 0 }, + { "min", 3, T_FUNC2, FN_MIN, 0 }, + { "n", 1, T_EDGEPT, 0, CP_N }, + { "ne", 2, T_EDGEPT, 0, CP_NE }, + { "north", 5, T_EDGEPT, 0, CP_N }, + { "nw", 2, T_EDGEPT, 0, CP_NW }, + { "of", 2, T_OF, 0, 0 }, + { "previous", 8, T_LAST, 0, 0, }, + { "print", 5, T_PRINT, 0, 0 }, + { "rad", 3, T_RADIUS, 0, 0 }, + { "radius", 6, T_RADIUS, 0, 0 }, + { "right", 5, T_RIGHT, DIR_RIGHT, CP_E }, + { "rjust", 5, T_RJUST, 0, 0 }, + { "s", 1, T_EDGEPT, 0, CP_S }, + { "same", 4, T_SAME, 0, 0 }, + { "se", 2, T_EDGEPT, 0, CP_SE }, + { "sin", 3, T_FUNC1, FN_SIN, 0 }, + { "small", 5, T_SMALL, 0, 0 }, + { "solid", 5, T_SOLID, 0, 0 }, + { "south", 5, T_EDGEPT, 0, CP_S }, + { "sqrt", 4, T_FUNC1, FN_SQRT, 0 }, + { "start", 5, T_START, 0, CP_START }, + { "sw", 2, T_EDGEPT, 0, CP_SW }, + { "t", 1, T_TOP, 0, CP_N }, + { "the", 3, T_THE, 0, 0 }, + { "then", 4, T_THEN, 0, 0 }, + { "thick", 5, T_THICK, 0, 0 }, + { "thickness", 9, T_THICKNESS, 0, 0 }, + { "thin", 4, T_THIN, 0, 0 }, + { "this", 4, T_THIS, 0, 0 }, + { "to", 2, T_TO, 0, 0 }, + { "top", 3, T_TOP, 0, CP_N }, + { "until", 5, T_UNTIL, 0, 0 }, + { "up", 2, T_UP, DIR_UP, 0 }, + { "vertex", 6, T_VERTEX, 0, 0 }, + { "w", 1, T_EDGEPT, 0, CP_W }, + { "way", 3, T_WAY, 0, 0 }, + { "west", 4, T_EDGEPT, 0, CP_W }, + { "wid", 3, T_WIDTH, 0, 0 }, + { "width", 5, T_WIDTH, 0, 0 }, + { "with", 4, T_WITH, 0, 0 }, + { "x", 1, T_X, 0, 0 }, + { "y", 1, T_Y, 0, 0 }, +}; + +/* +** Search a PikWordlist for the given keyword. Return a pointer to the +** keyword entry found. Or return 0 if not found. +*/ +static const PikWord *pik_find_word( + const char *zIn, /* Word to search for */ + int n, /* Length of zIn */ + const PikWord *aList, /* List to search */ + int nList /* Number of entries in aList */ +){ + int first = 0; + int last = nList-1; + while( first<=last ){ + int mid = (first + last)/2; + int sz = aList[mid].nChar; + int c = strncmp(zIn, aList[mid].zWord, szz character. Fill in other fields of the +** pToken object as appropriate. +*/ +static int pik_token_length(PToken *pToken, int bAllowCodeBlock){ + const unsigned char *z = (const unsigned char*)pToken->z; + int i; + unsigned char c, c2; + switch( z[0] ){ + case '\\': { + pToken->eType = T_WHITESPACE; + for(i=1; z[i]=='\r' || z[i]==' ' || z[i]=='\t'; i++){} + if( z[i]=='\n' ) return i+1; + pToken->eType = T_ERROR; + return 1; + } + case ';': + case '\n': { + pToken->eType = T_EOL; + return 1; + } + case '"': { + for(i=1; (c = z[i])!=0; i++){ + if( c=='\\' ){ + if( z[i+1]==0 ) break; + i++; + continue; + } + if( c=='"' ){ + pToken->eType = T_STRING; + return i+1; + } + } + pToken->eType = T_ERROR; + return i; + } + case ' ': + case '\t': + case '\f': + case '\r': { + for(i=1; (c = z[i])==' ' || c=='\t' || c=='\r' || c=='\t'; i++){} + pToken->eType = T_WHITESPACE; + return i; + } + case '#': { + for(i=1; (c = z[i])!=0 && c!='\n'; i++){} + pToken->eType = T_WHITESPACE; + /* If the comment is "#breakpoint" then invoke the pik_breakpoint() + ** routine. The pik_breakpoint() routie is a no-op that serves as + ** a convenient place to set a gdb breakpoint when debugging. */ + if( strncmp((const char*)z,"#breakpoint",11)==0 ) pik_breakpoint(z); + return i; + } + case '/': { + if( z[1]=='*' ){ + for(i=2; z[i]!=0 && (z[i]!='*' || z[i+1]!='/'); i++){} + if( z[i]=='*' ){ + pToken->eType = T_WHITESPACE; + return i+2; + }else{ + pToken->eType = T_ERROR; + return i; + } + }else if( z[1]=='/' ){ + for(i=2; z[i]!=0 && z[i]!='\n'; i++){} + pToken->eType = T_WHITESPACE; + return i; + }else if( z[1]=='=' ){ + pToken->eType = T_ASSIGN; + pToken->eCode = T_SLASH; + return 2; + }else{ + pToken->eType = T_SLASH; + return 1; + } + } + case '+': { + if( z[1]=='=' ){ + pToken->eType = T_ASSIGN; + pToken->eCode = T_PLUS; + return 2; + } + pToken->eType = T_PLUS; + return 1; + } + case '*': { + if( z[1]=='=' ){ + pToken->eType = T_ASSIGN; + pToken->eCode = T_STAR; + return 2; + } + pToken->eType = T_STAR; + return 1; + } + case '%': { pToken->eType = T_PERCENT; return 1; } + case '(': { pToken->eType = T_LP; return 1; } + case ')': { pToken->eType = T_RP; return 1; } + case '[': { pToken->eType = T_LB; return 1; } + case ']': { pToken->eType = T_RB; return 1; } + case ',': { pToken->eType = T_COMMA; return 1; } + case ':': { pToken->eType = T_COLON; return 1; } + case '>': { pToken->eType = T_GT; return 1; } + case '=': { + if( z[1]=='=' ){ + pToken->eType = T_EQ; + return 2; + } + pToken->eType = T_ASSIGN; + pToken->eCode = T_ASSIGN; + return 1; + } + case '-': { + if( z[1]=='>' ){ + pToken->eType = T_RARROW; + return 2; + }else if( z[1]=='=' ){ + pToken->eType = T_ASSIGN; + pToken->eCode = T_MINUS; + return 2; + }else{ + pToken->eType = T_MINUS; + return 1; + } + } + case '<': { + if( z[1]=='-' ){ + if( z[2]=='>' ){ + pToken->eType = T_LRARROW; + return 3; + }else{ + pToken->eType = T_LARROW; + return 2; + } + }else{ + pToken->eType = T_LT; + return 1; + } + } + case 0xe2: { + if( z[1]==0x86 ){ + if( z[2]==0x90 ){ + pToken->eType = T_LARROW; /* <- */ + return 3; + } + if( z[2]==0x92 ){ + pToken->eType = T_RARROW; /* -> */ + return 3; + } + if( z[2]==0x94 ){ + pToken->eType = T_LRARROW; /* <-> */ + return 3; + } + } + pToken->eType = T_ERROR; + return 1; + } + case '{': { + int len, depth; + i = 1; + if( bAllowCodeBlock ){ + depth = 1; + while( z[i] && depth>0 ){ + PToken x; + x.z = (char*)(z+i); + len = pik_token_length(&x, 0); + if( len==1 ){ + if( z[i]=='{' ) depth++; + if( z[i]=='}' ) depth--; + } + i += len; + } + }else{ + depth = 0; + } + if( depth ){ + pToken->eType = T_ERROR; + return 1; + } + pToken->eType = T_CODEBLOCK; + return i; + } + case '&': { + static const struct { + int nByte; /* Number of bytes in zEntity */ + int eCode; /* Corresponding token code */ + const char *zEntity; /* Name of the HTML entity */ + } aEntity[] = { + /* 123456789 1234567 */ + { 6, T_RARROW, "→" }, /* Same as -> */ + { 12, T_RARROW, "→" }, /* Same as -> */ + { 6, T_LARROW, "←" }, /* Same as <- */ + { 11, T_LARROW, "←" }, /* Same as <- */ + { 16, T_LRARROW, "↔" }, /* Same as <-> */ + }; + unsigned int i; + for(i=0; ieType = aEntity[i].eCode; + return aEntity[i].nByte; + } + } + pToken->eType = T_ERROR; + return 1; + } + default: { + c = z[0]; + if( c=='.' ){ + unsigned char c1 = z[1]; + if( islower(c1) ){ + const PikWord *pFound; + for(i=2; (c = z[i])>='a' && c<='z'; i++){} + pFound = pik_find_word((const char*)z+1, i-1, + pik_keywords, count(pik_keywords)); + if( pFound && (pFound->eEdge>0 || + pFound->eType==T_EDGEPT || + pFound->eType==T_START || + pFound->eType==T_END ) + ){ + /* Dot followed by something that is a 2-D place value */ + pToken->eType = T_DOT_E; + }else if( pFound && (pFound->eType==T_X || pFound->eType==T_Y) ){ + /* Dot followed by "x" or "y" */ + pToken->eType = T_DOT_XY; + }else{ + /* Any other "dot" */ + pToken->eType = T_DOT_L; + } + return 1; + }else if( isdigit(c1) ){ + i = 0; + /* no-op. Fall through to number handling */ + }else if( isupper(c1) ){ + for(i=2; (c = z[i])!=0 && (isalnum(c) || c=='_'); i++){} + pToken->eType = T_DOT_U; + return 1; + }else{ + pToken->eType = T_ERROR; + return 1; + } + } + if( (c>='0' && c<='9') || c=='.' ){ + int nDigit; + int isInt = 1; + if( c!='.' ){ + nDigit = 1; + for(i=1; (c = z[i])>='0' && c<='9'; i++){ nDigit++; } + if( i==1 && (c=='x' || c=='X') ){ + for(i=2; (c = z[i])!=0 && isxdigit(c); i++){} + pToken->eType = T_NUMBER; + return i; + } + }else{ + isInt = 0; + nDigit = 0; + i = 0; + } + if( c=='.' ){ + isInt = 0; + for(i++; (c = z[i])>='0' && c<='9'; i++){ nDigit++; } + } + if( nDigit==0 ){ + pToken->eType = T_ERROR; + return i; + } + if( c=='e' || c=='E' ){ + int iBefore = i; + i++; + c2 = z[i]; + if( c2=='+' || c2=='-' ){ + i++; + c2 = z[i]; + } + if( c2<'0' || c>'9' ){ + /* This is not an exp */ + i = iBefore; + }else{ + i++; + isInt = 0; + while( (c = z[i])>='0' && c<='9' ){ i++; } + } + } + c2 = c ? z[i+1] : 0; + if( isInt ){ + if( (c=='t' && c2=='h') + || (c=='r' && c2=='d') + || (c=='n' && c2=='d') + || (c=='s' && c2=='t') + ){ + pToken->eType = T_NTH; + return i+2; + } + } + if( (c=='i' && c2=='n') + || (c=='c' && c2=='m') + || (c=='m' && c2=='m') + || (c=='p' && c2=='t') + || (c=='p' && c2=='x') + || (c=='p' && c2=='c') + ){ + i += 2; + } + pToken->eType = T_NUMBER; + return i; + }else if( islower(c) ){ + const PikWord *pFound; + for(i=1; (c = z[i])!=0 && (isalnum(c) || c=='_'); i++){} + pFound = pik_find_word((const char*)z, i, + pik_keywords, count(pik_keywords)); + if( pFound ){ + pToken->eType = pFound->eType; + pToken->eCode = pFound->eCode; + pToken->eEdge = pFound->eEdge; + return i; + } + pToken->n = i; + if( pik_find_class(pToken)!=0 ){ + pToken->eType = T_CLASSNAME; + }else{ + pToken->eType = T_ID; + } + return i; + }else if( c>='A' && c<='Z' ){ + for(i=1; (c = z[i])!=0 && (isalnum(c) || c=='_'); i++){} + pToken->eType = T_PLACENAME; + return i; + }else if( c=='$' && z[1]>='1' && z[1]<='9' && !isdigit(z[2]) ){ + pToken->eType = T_PARAMETER; + pToken->eCode = z[1] - '1'; + return 2; + }else if( c=='_' || c=='$' || c=='@' ){ + for(i=1; (c = z[i])!=0 && (isalnum(c) || c=='_'); i++){} + pToken->eType = T_ID; + return i; + }else{ + pToken->eType = T_ERROR; + return 1; + } + } + } +} + +/* +** Return a pointer to the next non-whitespace token after pThis. +** This is used to help form error messages. +*/ +static PToken pik_next_semantic_token(PToken *pThis){ + PToken x; + int sz; + int i = pThis->n; + memset(&x, 0, sizeof(x)); + x.z = pThis->z; + while(1){ + x.z = pThis->z + i; + sz = pik_token_length(&x, 1); + if( x.eType!=T_WHITESPACE ){ + x.n = sz; + return x; + } + i += sz; + } +} + +/* Parser arguments to a macro invocation +** +** (arg1, arg2, ...) +** +** Arguments are comma-separated, except that commas within string +** literals or with (...), {...}, or [...] do not count. The argument +** list begins and ends with parentheses. There can be at most 9 +** arguments. +** +** Return the number of bytes in the argument list. +*/ +static unsigned int pik_parse_macro_args( + Pik *p, + const char *z, /* Start of the argument list */ + int n, /* Available bytes */ + PToken *args, /* Fill in with the arguments */ + PToken *pOuter /* Arguments of the next outer context, or NULL */ +){ + int nArg = 0; + int i, j, sz; + int iStart; + int depth = 0; + PToken x; + if( z[0]!='(' ) return 0; + args[0].z = z+1; + iStart = 1; + for(i=1; in>0 && isspace(t->z[0]) ){ t->n--; t->z++; } + while( t->n>0 && isspace(t->z[t->n-1]) ){ t->n--; } + if( t->n==2 && t->z[0]=='$' && t->z[1]>='1' && t->z[1]<='9' ){ + if( pOuter ) *t = pOuter[t->z[1]-'1']; + else t->n = 0; + } + } + return i+1; + } + x.z = z; + x.n = 1; + pik_error(p, &x, "unterminated macro argument list"); + return 0; +} + +/* +** Split up the content of a PToken into multiple tokens and +** send each to the parser. +*/ +void pik_tokenize(Pik *p, PToken *pIn, yyParser *pParser, PToken *aParam){ + unsigned int i; + int sz = 0; + PToken token; + PMacro *pMac; + for(i=0; in && pIn->z[i] && p->nErr==0; i+=sz){ + token.eCode = 0; + token.eEdge = 0; + token.z = pIn->z + i; + sz = pik_token_length(&token, 1); + if( token.eType==T_WHITESPACE ){ + /* no-op */ + }else if( sz>50000 ){ + token.n = 1; + pik_error(p, &token, "token is too long - max length 50000 bytes"); + break; + }else if( token.eType==T_ERROR ){ + token.n = (unsigned short)(sz & 0xffff); + pik_error(p, &token, "unrecognized token"); + break; + }else if( sz+i>pIn->n ){ + token.n = pIn->n - i; + pik_error(p, &token, "syntax error"); + break; + }else if( token.eType==T_PARAMETER ){ + /* Substitute a parameter into the input stream */ + if( aParam==0 || aParam[token.eCode].n==0 ){ + continue; + } + token.n = (unsigned short)(sz & 0xffff); + if( p->nCtx>=count(p->aCtx) ){ + pik_error(p, &token, "macros nested too deep"); + }else{ + p->aCtx[p->nCtx++] = token; + pik_tokenize(p, &aParam[token.eCode], pParser, 0); + p->nCtx--; + } + }else if( token.eType==T_ID + && (token.n = (unsigned short)(sz & 0xffff), + (pMac = pik_find_macro(p,&token))!=0) + ){ + PToken args[9]; + unsigned int j = i+sz; + if( pMac->inUse ){ + pik_error(p, &pMac->macroName, "recursive macro definition"); + break; + } + token.n = (short int)(sz & 0xffff); + if( p->nCtx>=count(p->aCtx) ){ + pik_error(p, &token, "macros nested too deep"); + break; + } + pMac->inUse = 1; + memset(args, 0, sizeof(args)); + p->aCtx[p->nCtx++] = token; + sz += pik_parse_macro_args(p, pIn->z+j, pIn->n-j, args, aParam); + pik_tokenize(p, &pMac->macroBody, pParser, args); + p->nCtx--; + pMac->inUse = 0; + }else{ +#if 0 + printf("******** Token %s (%d): \"%.*s\" **************\n", + yyTokenName[token.eType], token.eType, + (int)(isspace(token.z[0]) ? 0 : sz), token.z); +#endif + token.n = (unsigned short)(sz & 0xffff); + pik_parser(pParser, token.eType, token); + } + } +} + +/* +** Parse the PIKCHR script contained in zText[]. Return a rendering. Or +** if an error is encountered, return the error text. The error message +** is HTML formatted. So regardless of what happens, the return text +** is safe to be insertd into an HTML output stream. +** +** If pnWidth and pnHeight are not NULL, then this routine writes the +** width and height of the object into the integers that they +** point to. A value of -1 is written if an error is seen. +** +** If zClass is not NULL, then it is a class name to be included in +** the markup. +** +** The returned string is contained in memory obtained from malloc() +** and should be released by the caller. +*/ +char *pikchr( + const char *zText, /* Input PIKCHR source text. zero-terminated */ + const char *zClass, /* Add class="%s" to markup */ + unsigned int mFlags, /* Flags used to influence rendering behavior */ + int *pnWidth, /* Write width of here, if not NULL */ + int *pnHeight /* Write height here, if not NULL */ +){ + Pik s; + yyParser sParse; + + memset(&s, 0, sizeof(s)); + s.sIn.z = zText; + s.sIn.n = (unsigned int)strlen(zText); + s.eDir = DIR_RIGHT; + s.zClass = zClass; + s.mFlags = mFlags; + pik_parserInit(&sParse, &s); +#if 0 + pik_parserTrace(stdout, "parser: "); +#endif + pik_tokenize(&s, &s.sIn, &sParse, 0); + if( s.nErr==0 ){ + PToken token; + memset(&token,0,sizeof(token)); + token.z = zText + (s.sIn.n>0 ? s.sIn.n-1 : 0); + token.n = 1; + pik_parser(&sParse, 0, token); + } + pik_parserFinalize(&sParse); + if( s.zOut==0 && s.nErr==0 ){ + pik_append(&s, "\n", -1); + } + while( s.pVar ){ + PVar *pNext = s.pVar->pNext; + free(s.pVar); + s.pVar = pNext; + } + while( s.pMacros ){ + PMacro *pNext = s.pMacros->pNext; + free(s.pMacros); + s.pMacros = pNext; + } + if( pnWidth ) *pnWidth = s.nErr ? -1 : s.wSVG; + if( pnHeight ) *pnHeight = s.nErr ? -1 : s.hSVG; + if( s.zOut ){ + s.zOut[s.nOut] = 0; + s.zOut = realloc(s.zOut, s.nOut+1); + } + return s.zOut; +} + +#if defined(PIKCHR_FUZZ) +#include +int LLVMFuzzerTestOneInput(const uint8_t *aData, size_t nByte){ + int w,h; + char *zIn, *zOut; + unsigned int mFlags = nByte & 3; + zIn = malloc( nByte + 1 ); + if( zIn==0 ) return 0; + memcpy(zIn, aData, nByte); + zIn[nByte] = 0; + zOut = pikchr(zIn, "pikchr", mFlags, &w, &h); + free(zIn); + free(zOut); + return 0; +} +#endif /* PIKCHR_FUZZ */ + +#if defined(PIKCHR_SHELL) +/* Print a usage comment for the shell and exit. */ +static void usage(const char *argv0){ + fprintf(stderr, "usage: %s [OPTIONS] FILE ...\n", argv0); + fprintf(stderr, + "Convert Pikchr input files into SVG. Filename \"-\" means stdin.\n" + "Options:\n" + " --dont-stop Process all files even if earlier files have errors\n" + " --svg-only Omit raw SVG without the HTML wrapper\n" + ); + exit(1); +} + +/* Send text to standard output, but escape HTML markup */ +static void print_escape_html(const char *z){ + int j; + char c; + while( z[0]!=0 ){ + for(j=0; (c = z[j])!=0 && c!='<' && c!='>' && c!='&'; j++){} + if( j ) printf("%.*s", j, z); + z += j+1; + j = -1; + if( c=='<' ){ + printf("<"); + }else if( c=='>' ){ + printf(">"); + }else if( c=='&' ){ + printf("&"); + }else if( c==0 ){ + break; + } + } +} + +/* Read the content of file zFilename into memory obtained from malloc() +** Return the memory. If something goes wrong (ex: the file does not exist +** or cannot be opened) put an error message on stderr and return NULL. +** +** If the filename is "-" read stdin. +*/ +static char *readFile(const char *zFilename){ + FILE *in; + size_t n; + size_t nUsed = 0; + size_t nAlloc = 0; + char *z = 0, *zNew = 0; + in = strcmp(zFilename,"-")==0 ? stdin : fopen(zFilename, "rb"); + if( in==0 ){ + fprintf(stderr, "cannot open \"%s\" for reading\n", zFilename); + return 0; + } + while(1){ + if( nUsed+2>=nAlloc ){ + nAlloc = nAlloc*2 + 4000; + zNew = realloc(z, nAlloc); + } + if( zNew==0 ){ + free(z); + fprintf(stderr, "out of memory trying to allocate %lld bytes\n", + (long long int)nAlloc); + exit(1); + } + z = zNew; + n = fread(z+nUsed, 1, nAlloc-nUsed-1, in); + if( n<=0 ){ + break; + } + nUsed += n; + } + if( in!=stdin ) fclose(in); + z[nUsed] = 0; + return z; +} + + +/* Testing interface +** +** Generate HTML on standard output that displays both the original +** input text and the rendered SVG for all files named on the command +** line. +*/ +int main(int argc, char **argv){ + int i; + int bSvgOnly = 0; /* Output SVG only. No HTML wrapper */ + int bDontStop = 0; /* Continue in spite of errors */ + int exitCode = 0; /* What to return */ + int mFlags = 0; /* mFlags argument to pikchr() */ + const char *zStyle = ""; /* Extra styling */ + const char *zHtmlHdr = + "\n" + "\n" + "\nPIKCHR Test\n" + "\n" + "\n" + "\n" + "\n" + "\n" + ; + if( argc<2 ) usage(argv[0]); + for(i=1; iFile %s

\n", argv[i]); + if( w<0 ){ + printf("

ERROR

\n%s\n", zOut); + }else{ + printf("
\n",i,i); + printf("
\n", + w,zStyle); + printf("%s
\n", zOut); + printf("\n
\n"); + } + } + free(zOut); + free(zIn); + } + if( !bSvgOnly ){ + printf("\n"); + } + return exitCode ? EXIT_FAILURE : EXIT_SUCCESS; +} +#endif /* PIKCHR_SHELL */ + +#ifdef PIKCHR_TCL +#include +/* +** An interface to TCL +** +** TCL command: pikchr $INPUTTEXT +** +** Returns a list of 3 elements which are the output text, the width, and +** the height. +** +** Register the "pikchr" command by invoking Pikchr_Init(Tcl_Interp*). Or +** compile this source file as a shared library and load it using the +** "load" command of Tcl. +** +** Compile this source-code file into a shared library using a command +** similar to this: +** +** gcc -c pikchr.so -DPIKCHR_TCL -shared pikchr.c +*/ +static int pik_tcl_command( + ClientData clientData, /* Not Used */ + Tcl_Interp *interp, /* The TCL interpreter that invoked this command */ + int objc, /* Number of arguments */ + Tcl_Obj *CONST objv[] /* Command arguments */ +){ + int w, h; /* Width and height of the pikchr */ + const char *zIn; /* Source text input */ + char *zOut; /* SVG output text */ + Tcl_Obj *pRes; /* The result TCL object */ + + (void)clientData; + if( objc!=2 ){ + Tcl_WrongNumArgs(interp, 1, objv, "PIKCHR_SOURCE_TEXT"); + return TCL_ERROR; + } + zIn = Tcl_GetString(objv[1]); + w = h = 0; + zOut = pikchr(zIn, "pikchr", 0, &w, &h); + if( zOut==0 ){ + return TCL_ERROR; /* Out of memory */ + } + pRes = Tcl_NewObj(); + Tcl_ListObjAppendElement(0, pRes, Tcl_NewStringObj(zOut, -1)); + free(zOut); + Tcl_ListObjAppendElement(0, pRes, Tcl_NewIntObj(w)); + Tcl_ListObjAppendElement(0, pRes, Tcl_NewIntObj(h)); + Tcl_SetObjResult(interp, pRes); + return TCL_OK; +} + +#ifndef PACKAGE_NAME +# define PACKAGE_NAME "pikchr" +#endif +#ifndef PACKAGE_VERSION +# define PACKAGE_VERSION "1.0" +#endif + +/* Invoke this routine to register the "pikchr" command with the interpreter +** given in the argument */ +int Pikchr_Init(Tcl_Interp *interp){ + Tcl_CreateObjCommand(interp, "pikchr", pik_tcl_command, 0, 0); + Tcl_PkgProvide(interp, PACKAGE_NAME, PACKAGE_VERSION); + return TCL_OK; +} + + +#endif /* PIKCHR_TCL */ + + +#line 8111 "pikchr.c" ADDED extsrc/shell.c Index: extsrc/shell.c ================================================================== --- /dev/null +++ extsrc/shell.c @@ -0,0 +1,22890 @@ +/* DO NOT EDIT! +** This file is automatically generated by the script in the canonical +** SQLite source tree at tool/mkshellc.tcl. That script combines source +** code from various constituent source files of SQLite into this single +** "shell.c" file used to implement the SQLite command-line shell. +** +** Most of the code found below comes from the "src/shell.c.in" file in +** the canonical SQLite source tree. That main file contains "INCLUDE" +** lines that specify other files in the canonical source tree that are +** inserted to getnerate this complete program source file. +** +** The code from multiple files is combined into this single "shell.c" +** source file to help make the command-line program easier to compile. +** +** To modify this program, get a copy of the canonical SQLite source tree, +** edit the src/shell.c.in" and/or some of the other files that are included +** by "src/shell.c.in", then rerun the tool/mkshellc.tcl script. +*/ +/* +** 2001 September 15 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +************************************************************************* +** This file contains code to implement the "sqlite" command line +** utility for accessing SQLite databases. +*/ +#if (defined(_WIN32) || defined(WIN32)) && !defined(_CRT_SECURE_NO_WARNINGS) +/* This needs to come before any includes for MSVC compiler */ +#define _CRT_SECURE_NO_WARNINGS +#endif + +/* +** Optionally #include a user-defined header, whereby compilation options +** may be set prior to where they take effect, but after platform setup. +** If SQLITE_CUSTOM_INCLUDE=? is defined, its value names the #include +** file. Note that this macro has a like effect on sqlite3.c compilation. +*/ +#ifdef SQLITE_CUSTOM_INCLUDE +# define INC_STRINGIFY_(f) #f +# define INC_STRINGIFY(f) INC_STRINGIFY_(f) +# include INC_STRINGIFY(SQLITE_CUSTOM_INCLUDE) +#endif + +/* +** Determine if we are dealing with WinRT, which provides only a subset of +** the full Win32 API. +*/ +#if !defined(SQLITE_OS_WINRT) +# define SQLITE_OS_WINRT 0 +#endif + +/* +** Warning pragmas copied from msvc.h in the core. +*/ +#if defined(_MSC_VER) +#pragma warning(disable : 4054) +#pragma warning(disable : 4055) +#pragma warning(disable : 4100) +#pragma warning(disable : 4127) +#pragma warning(disable : 4130) +#pragma warning(disable : 4152) +#pragma warning(disable : 4189) +#pragma warning(disable : 4206) +#pragma warning(disable : 4210) +#pragma warning(disable : 4232) +#pragma warning(disable : 4244) +#pragma warning(disable : 4305) +#pragma warning(disable : 4306) +#pragma warning(disable : 4702) +#pragma warning(disable : 4706) +#endif /* defined(_MSC_VER) */ + +/* +** No support for loadable extensions in VxWorks. +*/ +#if (defined(__RTP__) || defined(_WRS_KERNEL)) && !SQLITE_OMIT_LOAD_EXTENSION +# define SQLITE_OMIT_LOAD_EXTENSION 1 +#endif + +/* +** Enable large-file support for fopen() and friends on unix. +*/ +#ifndef SQLITE_DISABLE_LFS +# define _LARGE_FILE 1 +# ifndef _FILE_OFFSET_BITS +# define _FILE_OFFSET_BITS 64 +# endif +# define _LARGEFILE_SOURCE 1 +#endif + +#include +#include +#include +#include +#include "sqlite3.h" +typedef sqlite3_int64 i64; +typedef sqlite3_uint64 u64; +typedef unsigned char u8; +#if SQLITE_USER_AUTHENTICATION +# include "sqlite3userauth.h" +#endif +#include +#include + +#if !defined(_WIN32) && !defined(WIN32) +# include +# if !defined(__RTP__) && !defined(_WRS_KERNEL) +# include +# endif +#endif +#if (!defined(_WIN32) && !defined(WIN32)) || defined(__MINGW32__) +# include +# include +# define GETPID getpid +# if defined(__MINGW32__) +# define DIRENT dirent +# ifndef S_ISLNK +# define S_ISLNK(mode) (0) +# endif +# endif +#else +# define GETPID (int)GetCurrentProcessId +#endif +#include +#include + +#if HAVE_READLINE +# include +# include +#endif + +#if HAVE_EDITLINE +# include +#endif + +#if HAVE_EDITLINE || HAVE_READLINE + +# define shell_add_history(X) add_history(X) +# define shell_read_history(X) read_history(X) +# define shell_write_history(X) write_history(X) +# define shell_stifle_history(X) stifle_history(X) +# define shell_readline(X) readline(X) + +#elif HAVE_LINENOISE + +# include "linenoise.h" +# define shell_add_history(X) linenoiseHistoryAdd(X) +# define shell_read_history(X) linenoiseHistoryLoad(X) +# define shell_write_history(X) linenoiseHistorySave(X) +# define shell_stifle_history(X) linenoiseHistorySetMaxLen(X) +# define shell_readline(X) linenoise(X) + +#else + +# define shell_read_history(X) +# define shell_write_history(X) +# define shell_stifle_history(X) + +# define SHELL_USE_LOCAL_GETLINE 1 +#endif + + +#if defined(_WIN32) || defined(WIN32) +# if SQLITE_OS_WINRT +# define SQLITE_OMIT_POPEN 1 +# else +# include +# include +# define isatty(h) _isatty(h) +# ifndef access +# define access(f,m) _access((f),(m)) +# endif +# ifndef unlink +# define unlink _unlink +# endif +# ifndef strdup +# define strdup _strdup +# endif +# undef popen +# define popen _popen +# undef pclose +# define pclose _pclose +# endif +#else + /* Make sure isatty() has a prototype. */ + extern int isatty(int); + +# if !defined(__RTP__) && !defined(_WRS_KERNEL) + /* popen and pclose are not C89 functions and so are + ** sometimes omitted from the header */ + extern FILE *popen(const char*,const char*); + extern int pclose(FILE*); +# else +# define SQLITE_OMIT_POPEN 1 +# endif +#endif + +#if defined(_WIN32_WCE) +/* Windows CE (arm-wince-mingw32ce-gcc) does not provide isatty() + * thus we always assume that we have a console. That can be + * overridden with the -batch command line option. + */ +#define isatty(x) 1 +#endif + +/* ctype macros that work with signed characters */ +#define IsSpace(X) isspace((unsigned char)X) +#define IsDigit(X) isdigit((unsigned char)X) +#define ToLower(X) (char)tolower((unsigned char)X) + +#if defined(_WIN32) || defined(WIN32) +#if SQLITE_OS_WINRT +#include +#endif +#include + +/* string conversion routines only needed on Win32 */ +extern char *sqlite3_win32_unicode_to_utf8(LPCWSTR); +extern char *sqlite3_win32_mbcs_to_utf8_v2(const char *, int); +extern char *sqlite3_win32_utf8_to_mbcs_v2(const char *, int); +extern LPWSTR sqlite3_win32_utf8_to_unicode(const char *zText); +#endif + +/* On Windows, we normally run with output mode of TEXT so that \n characters +** are automatically translated into \r\n. However, this behavior needs +** to be disabled in some cases (ex: when generating CSV output and when +** rendering quoted strings that contain \n characters). The following +** routines take care of that. +*/ +#if (defined(_WIN32) || defined(WIN32)) && !SQLITE_OS_WINRT +static void setBinaryMode(FILE *file, int isOutput){ + if( isOutput ) fflush(file); + _setmode(_fileno(file), _O_BINARY); +} +static void setTextMode(FILE *file, int isOutput){ + if( isOutput ) fflush(file); + _setmode(_fileno(file), _O_TEXT); +} +#else +# define setBinaryMode(X,Y) +# define setTextMode(X,Y) +#endif + + +/* True if the timer is enabled */ +static int enableTimer = 0; + +/* Return the current wall-clock time */ +static sqlite3_int64 timeOfDay(void){ + static sqlite3_vfs *clockVfs = 0; + sqlite3_int64 t; + if( clockVfs==0 ) clockVfs = sqlite3_vfs_find(0); + if( clockVfs==0 ) return 0; /* Never actually happens */ + if( clockVfs->iVersion>=2 && clockVfs->xCurrentTimeInt64!=0 ){ + clockVfs->xCurrentTimeInt64(clockVfs, &t); + }else{ + double r; + clockVfs->xCurrentTime(clockVfs, &r); + t = (sqlite3_int64)(r*86400000.0); + } + return t; +} + +#if !defined(_WIN32) && !defined(WIN32) && !defined(__minux) +#include +#include + +/* VxWorks does not support getrusage() as far as we can determine */ +#if defined(_WRS_KERNEL) || defined(__RTP__) +struct rusage { + struct timeval ru_utime; /* user CPU time used */ + struct timeval ru_stime; /* system CPU time used */ +}; +#define getrusage(A,B) memset(B,0,sizeof(*B)) +#endif + +/* Saved resource information for the beginning of an operation */ +static struct rusage sBegin; /* CPU time at start */ +static sqlite3_int64 iBegin; /* Wall-clock time at start */ + +/* +** Begin timing an operation +*/ +static void beginTimer(void){ + if( enableTimer ){ + getrusage(RUSAGE_SELF, &sBegin); + iBegin = timeOfDay(); + } +} + +/* Return the difference of two time_structs in seconds */ +static double timeDiff(struct timeval *pStart, struct timeval *pEnd){ + return (pEnd->tv_usec - pStart->tv_usec)*0.000001 + + (double)(pEnd->tv_sec - pStart->tv_sec); +} + +/* +** Print the timing results. +*/ +static void endTimer(void){ + if( enableTimer ){ + sqlite3_int64 iEnd = timeOfDay(); + struct rusage sEnd; + getrusage(RUSAGE_SELF, &sEnd); + printf("Run Time: real %.3f user %f sys %f\n", + (iEnd - iBegin)*0.001, + timeDiff(&sBegin.ru_utime, &sEnd.ru_utime), + timeDiff(&sBegin.ru_stime, &sEnd.ru_stime)); + } +} + +#define BEGIN_TIMER beginTimer() +#define END_TIMER endTimer() +#define HAS_TIMER 1 + +#elif (defined(_WIN32) || defined(WIN32)) + +/* Saved resource information for the beginning of an operation */ +static HANDLE hProcess; +static FILETIME ftKernelBegin; +static FILETIME ftUserBegin; +static sqlite3_int64 ftWallBegin; +typedef BOOL (WINAPI *GETPROCTIMES)(HANDLE, LPFILETIME, LPFILETIME, + LPFILETIME, LPFILETIME); +static GETPROCTIMES getProcessTimesAddr = NULL; + +/* +** Check to see if we have timer support. Return 1 if necessary +** support found (or found previously). +*/ +static int hasTimer(void){ + if( getProcessTimesAddr ){ + return 1; + } else { +#if !SQLITE_OS_WINRT + /* GetProcessTimes() isn't supported in WIN95 and some other Windows + ** versions. See if the version we are running on has it, and if it + ** does, save off a pointer to it and the current process handle. + */ + hProcess = GetCurrentProcess(); + if( hProcess ){ + HINSTANCE hinstLib = LoadLibrary(TEXT("Kernel32.dll")); + if( NULL != hinstLib ){ + getProcessTimesAddr = + (GETPROCTIMES) GetProcAddress(hinstLib, "GetProcessTimes"); + if( NULL != getProcessTimesAddr ){ + return 1; + } + FreeLibrary(hinstLib); + } + } +#endif + } + return 0; +} + +/* +** Begin timing an operation +*/ +static void beginTimer(void){ + if( enableTimer && getProcessTimesAddr ){ + FILETIME ftCreation, ftExit; + getProcessTimesAddr(hProcess,&ftCreation,&ftExit, + &ftKernelBegin,&ftUserBegin); + ftWallBegin = timeOfDay(); + } +} + +/* Return the difference of two FILETIME structs in seconds */ +static double timeDiff(FILETIME *pStart, FILETIME *pEnd){ + sqlite_int64 i64Start = *((sqlite_int64 *) pStart); + sqlite_int64 i64End = *((sqlite_int64 *) pEnd); + return (double) ((i64End - i64Start) / 10000000.0); +} + +/* +** Print the timing results. +*/ +static void endTimer(void){ + if( enableTimer && getProcessTimesAddr){ + FILETIME ftCreation, ftExit, ftKernelEnd, ftUserEnd; + sqlite3_int64 ftWallEnd = timeOfDay(); + getProcessTimesAddr(hProcess,&ftCreation,&ftExit,&ftKernelEnd,&ftUserEnd); + printf("Run Time: real %.3f user %f sys %f\n", + (ftWallEnd - ftWallBegin)*0.001, + timeDiff(&ftUserBegin, &ftUserEnd), + timeDiff(&ftKernelBegin, &ftKernelEnd)); + } +} + +#define BEGIN_TIMER beginTimer() +#define END_TIMER endTimer() +#define HAS_TIMER hasTimer() + +#else +#define BEGIN_TIMER +#define END_TIMER +#define HAS_TIMER 0 +#endif + +/* +** Used to prevent warnings about unused parameters +*/ +#define UNUSED_PARAMETER(x) (void)(x) + +/* +** Number of elements in an array +*/ +#define ArraySize(X) (int)(sizeof(X)/sizeof(X[0])) + +/* +** If the following flag is set, then command execution stops +** at an error if we are not interactive. +*/ +static int bail_on_error = 0; + +/* +** Threat stdin as an interactive input if the following variable +** is true. Otherwise, assume stdin is connected to a file or pipe. +*/ +static int stdin_is_interactive = 1; + +/* +** On Windows systems we have to know if standard output is a console +** in order to translate UTF-8 into MBCS. The following variable is +** true if translation is required. +*/ +static int stdout_is_console = 1; + +/* +** The following is the open SQLite database. We make a pointer +** to this database a static variable so that it can be accessed +** by the SIGINT handler to interrupt database processing. +*/ +static sqlite3 *globalDb = 0; + +/* +** True if an interrupt (Control-C) has been received. +*/ +static volatile int seenInterrupt = 0; + +#ifdef SQLITE_DEBUG +/* +** Out-of-memory simulator variables +*/ +static unsigned int oomCounter = 0; /* Simulate OOM when equals 1 */ +static unsigned int oomRepeat = 0; /* Number of OOMs in a row */ +static void*(*defaultMalloc)(int) = 0; /* The low-level malloc routine */ +#endif /* SQLITE_DEBUG */ + +/* +** This is the name of our program. It is set in main(), used +** in a number of other places, mostly for error messages. +*/ +static char *Argv0; + +/* +** Prompt strings. Initialized in main. Settable with +** .prompt main continue +*/ +static char mainPrompt[20]; /* First line prompt. default: "sqlite> "*/ +static char continuePrompt[20]; /* Continuation prompt. default: " ...> " */ + +/* +** Render output like fprintf(). Except, if the output is going to the +** console and if this is running on a Windows machine, translate the +** output from UTF-8 into MBCS. +*/ +#if defined(_WIN32) || defined(WIN32) +void utf8_printf(FILE *out, const char *zFormat, ...){ + va_list ap; + va_start(ap, zFormat); + if( stdout_is_console && (out==stdout || out==stderr) ){ + char *z1 = sqlite3_vmprintf(zFormat, ap); + char *z2 = sqlite3_win32_utf8_to_mbcs_v2(z1, 0); + sqlite3_free(z1); + fputs(z2, out); + sqlite3_free(z2); + }else{ + vfprintf(out, zFormat, ap); + } + va_end(ap); +} +#elif !defined(utf8_printf) +# define utf8_printf fprintf +#endif + +/* +** Render output like fprintf(). This should not be used on anything that +** includes string formatting (e.g. "%s"). +*/ +#if !defined(raw_printf) +# define raw_printf fprintf +#endif + +/* Indicate out-of-memory and exit. */ +static void shell_out_of_memory(void){ + raw_printf(stderr,"Error: out of memory\n"); + exit(1); +} + +#ifdef SQLITE_DEBUG +/* This routine is called when a simulated OOM occurs. It is broken +** out as a separate routine to make it easy to set a breakpoint on +** the OOM +*/ +void shellOomFault(void){ + if( oomRepeat>0 ){ + oomRepeat--; + }else{ + oomCounter--; + } +} +#endif /* SQLITE_DEBUG */ + +#ifdef SQLITE_DEBUG +/* This routine is a replacement malloc() that is used to simulate +** Out-Of-Memory (OOM) errors for testing purposes. +*/ +static void *oomMalloc(int nByte){ + if( oomCounter ){ + if( oomCounter==1 ){ + shellOomFault(); + return 0; + }else{ + oomCounter--; + } + } + return defaultMalloc(nByte); +} +#endif /* SQLITE_DEBUG */ + +#ifdef SQLITE_DEBUG +/* Register the OOM simulator. This must occur before any memory +** allocations */ +static void registerOomSimulator(void){ + sqlite3_mem_methods mem; + sqlite3_config(SQLITE_CONFIG_GETMALLOC, &mem); + defaultMalloc = mem.xMalloc; + mem.xMalloc = oomMalloc; + sqlite3_config(SQLITE_CONFIG_MALLOC, &mem); +} +#endif + +/* +** Write I/O traces to the following stream. +*/ +#ifdef SQLITE_ENABLE_IOTRACE +static FILE *iotrace = 0; +#endif + +/* +** This routine works like printf in that its first argument is a +** format string and subsequent arguments are values to be substituted +** in place of % fields. The result of formatting this string +** is written to iotrace. +*/ +#ifdef SQLITE_ENABLE_IOTRACE +static void SQLITE_CDECL iotracePrintf(const char *zFormat, ...){ + va_list ap; + char *z; + if( iotrace==0 ) return; + va_start(ap, zFormat); + z = sqlite3_vmprintf(zFormat, ap); + va_end(ap); + utf8_printf(iotrace, "%s", z); + sqlite3_free(z); +} +#endif + +/* +** Output string zUtf to stream pOut as w characters. If w is negative, +** then right-justify the text. W is the width in UTF-8 characters, not +** in bytes. This is different from the %*.*s specification in printf +** since with %*.*s the width is measured in bytes, not characters. +*/ +static void utf8_width_print(FILE *pOut, int w, const char *zUtf){ + int i; + int n; + int aw = w<0 ? -w : w; + for(i=n=0; zUtf[i]; i++){ + if( (zUtf[i]&0xc0)!=0x80 ){ + n++; + if( n==aw ){ + do{ i++; }while( (zUtf[i]&0xc0)==0x80 ); + break; + } + } + } + if( n>=aw ){ + utf8_printf(pOut, "%.*s", i, zUtf); + }else if( w<0 ){ + utf8_printf(pOut, "%*s%s", aw-n, "", zUtf); + }else{ + utf8_printf(pOut, "%s%*s", zUtf, aw-n, ""); + } +} + + +/* +** Determines if a string is a number of not. +*/ +static int isNumber(const char *z, int *realnum){ + if( *z=='-' || *z=='+' ) z++; + if( !IsDigit(*z) ){ + return 0; + } + z++; + if( realnum ) *realnum = 0; + while( IsDigit(*z) ){ z++; } + if( *z=='.' ){ + z++; + if( !IsDigit(*z) ) return 0; + while( IsDigit(*z) ){ z++; } + if( realnum ) *realnum = 1; + } + if( *z=='e' || *z=='E' ){ + z++; + if( *z=='+' || *z=='-' ) z++; + if( !IsDigit(*z) ) return 0; + while( IsDigit(*z) ){ z++; } + if( realnum ) *realnum = 1; + } + return *z==0; +} + +/* +** Compute a string length that is limited to what can be stored in +** lower 30 bits of a 32-bit signed integer. +*/ +static int strlen30(const char *z){ + const char *z2 = z; + while( *z2 ){ z2++; } + return 0x3fffffff & (int)(z2 - z); +} + +/* +** Return the length of a string in characters. Multibyte UTF8 characters +** count as a single character. +*/ +static int strlenChar(const char *z){ + int n = 0; + while( *z ){ + if( (0xc0&*(z++))!=0x80 ) n++; + } + return n; +} + +/* +** Return open FILE * if zFile exists, can be opened for read +** and is an ordinary file or a character stream source. +** Otherwise return 0. +*/ +static FILE * openChrSource(const char *zFile){ +#ifdef _WIN32 + struct _stat x = {0}; +# define STAT_CHR_SRC(mode) ((mode & (_S_IFCHR|_S_IFIFO|_S_IFREG))!=0) + /* On Windows, open first, then check the stream nature. This order + ** is necessary because _stat() and sibs, when checking a named pipe, + ** effectively break the pipe as its supplier sees it. */ + FILE *rv = fopen(zFile, "rb"); + if( rv==0 ) return 0; + if( _fstat(_fileno(rv), &x) != 0 + || !STAT_CHR_SRC(x.st_mode)){ + fclose(rv); + rv = 0; + } + return rv; +#else + struct stat x = {0}; + int rc = stat(zFile, &x); +# define STAT_CHR_SRC(mode) (S_ISREG(mode)||S_ISFIFO(mode)||S_ISCHR(mode)) + if( rc!=0 ) return 0; + if( STAT_CHR_SRC(x.st_mode) ){ + return fopen(zFile, "rb"); + }else{ + return 0; + } +#endif +#undef STAT_CHR_SRC +} + +/* +** This routine reads a line of text from FILE in, stores +** the text in memory obtained from malloc() and returns a pointer +** to the text. NULL is returned at end of file, or if malloc() +** fails. +** +** If zLine is not NULL then it is a malloced buffer returned from +** a previous call to this routine that may be reused. +*/ +static char *local_getline(char *zLine, FILE *in){ + int nLine = zLine==0 ? 0 : 100; + int n = 0; + + while( 1 ){ + if( n+100>nLine ){ + nLine = nLine*2 + 100; + zLine = realloc(zLine, nLine); + if( zLine==0 ) shell_out_of_memory(); + } + if( fgets(&zLine[n], nLine - n, in)==0 ){ + if( n==0 ){ + free(zLine); + return 0; + } + zLine[n] = 0; + break; + } + while( zLine[n] ) n++; + if( n>0 && zLine[n-1]=='\n' ){ + n--; + if( n>0 && zLine[n-1]=='\r' ) n--; + zLine[n] = 0; + break; + } + } +#if defined(_WIN32) || defined(WIN32) + /* For interactive input on Windows systems, translate the + ** multi-byte characterset characters into UTF-8. */ + if( stdin_is_interactive && in==stdin ){ + char *zTrans = sqlite3_win32_mbcs_to_utf8_v2(zLine, 0); + if( zTrans ){ + int nTrans = strlen30(zTrans)+1; + if( nTrans>nLine ){ + zLine = realloc(zLine, nTrans); + if( zLine==0 ) shell_out_of_memory(); + } + memcpy(zLine, zTrans, nTrans); + sqlite3_free(zTrans); + } + } +#endif /* defined(_WIN32) || defined(WIN32) */ + return zLine; +} + +/* +** Retrieve a single line of input text. +** +** If in==0 then read from standard input and prompt before each line. +** If isContinuation is true, then a continuation prompt is appropriate. +** If isContinuation is zero, then the main prompt should be used. +** +** If zPrior is not NULL then it is a buffer from a prior call to this +** routine that can be reused. +** +** The result is stored in space obtained from malloc() and must either +** be freed by the caller or else passed back into this routine via the +** zPrior argument for reuse. +*/ +static char *one_input_line(FILE *in, char *zPrior, int isContinuation){ + char *zPrompt; + char *zResult; + if( in!=0 ){ + zResult = local_getline(zPrior, in); + }else{ + zPrompt = isContinuation ? continuePrompt : mainPrompt; +#if SHELL_USE_LOCAL_GETLINE + printf("%s", zPrompt); + fflush(stdout); + zResult = local_getline(zPrior, stdin); +#else + free(zPrior); + zResult = shell_readline(zPrompt); + if( zResult && *zResult ) shell_add_history(zResult); +#endif + } + return zResult; +} + + +/* +** Return the value of a hexadecimal digit. Return -1 if the input +** is not a hex digit. +*/ +static int hexDigitValue(char c){ + if( c>='0' && c<='9' ) return c - '0'; + if( c>='a' && c<='f' ) return c - 'a' + 10; + if( c>='A' && c<='F' ) return c - 'A' + 10; + return -1; +} + +/* +** Interpret zArg as an integer value, possibly with suffixes. +*/ +static sqlite3_int64 integerValue(const char *zArg){ + sqlite3_int64 v = 0; + static const struct { char *zSuffix; int iMult; } aMult[] = { + { "KiB", 1024 }, + { "MiB", 1024*1024 }, + { "GiB", 1024*1024*1024 }, + { "KB", 1000 }, + { "MB", 1000000 }, + { "GB", 1000000000 }, + { "K", 1000 }, + { "M", 1000000 }, + { "G", 1000000000 }, + }; + int i; + int isNeg = 0; + if( zArg[0]=='-' ){ + isNeg = 1; + zArg++; + }else if( zArg[0]=='+' ){ + zArg++; + } + if( zArg[0]=='0' && zArg[1]=='x' ){ + int x; + zArg += 2; + while( (x = hexDigitValue(zArg[0]))>=0 ){ + v = (v<<4) + x; + zArg++; + } + }else{ + while( IsDigit(zArg[0]) ){ + v = v*10 + zArg[0] - '0'; + zArg++; + } + } + for(i=0; iz); + initText(p); +} + +/* zIn is either a pointer to a NULL-terminated string in memory obtained +** from malloc(), or a NULL pointer. The string pointed to by zAppend is +** added to zIn, and the result returned in memory obtained from malloc(). +** zIn, if it was not NULL, is freed. +** +** If the third argument, quote, is not '\0', then it is used as a +** quote character for zAppend. +*/ +static void appendText(ShellText *p, char const *zAppend, char quote){ + int len; + int i; + int nAppend = strlen30(zAppend); + + len = nAppend+p->n+1; + if( quote ){ + len += 2; + for(i=0; iz==0 || p->n+len>=p->nAlloc ){ + p->nAlloc = p->nAlloc*2 + len + 20; + p->z = realloc(p->z, p->nAlloc); + if( p->z==0 ) shell_out_of_memory(); + } + + if( quote ){ + char *zCsr = p->z+p->n; + *zCsr++ = quote; + for(i=0; in = (int)(zCsr - p->z); + *zCsr = '\0'; + }else{ + memcpy(p->z+p->n, zAppend, nAppend); + p->n += nAppend; + p->z[p->n] = '\0'; + } +} + +/* +** Attempt to determine if identifier zName needs to be quoted, either +** because it contains non-alphanumeric characters, or because it is an +** SQLite keyword. Be conservative in this estimate: When in doubt assume +** that quoting is required. +** +** Return '"' if quoting is required. Return 0 if no quoting is required. +*/ +static char quoteChar(const char *zName){ + int i; + if( !isalpha((unsigned char)zName[0]) && zName[0]!='_' ) return '"'; + for(i=0; zName[i]; i++){ + if( !isalnum((unsigned char)zName[i]) && zName[i]!='_' ) return '"'; + } + return sqlite3_keyword_check(zName, i) ? '"' : 0; +} + +/* +** Construct a fake object name and column list to describe the structure +** of the view, virtual table, or table valued function zSchema.zName. +*/ +static char *shellFakeSchema( + sqlite3 *db, /* The database connection containing the vtab */ + const char *zSchema, /* Schema of the database holding the vtab */ + const char *zName /* The name of the virtual table */ +){ + sqlite3_stmt *pStmt = 0; + char *zSql; + ShellText s; + char cQuote; + char *zDiv = "("; + int nRow = 0; + + zSql = sqlite3_mprintf("PRAGMA \"%w\".table_info=%Q;", + zSchema ? zSchema : "main", zName); + sqlite3_prepare_v2(db, zSql, -1, &pStmt, 0); + sqlite3_free(zSql); + initText(&s); + if( zSchema ){ + cQuote = quoteChar(zSchema); + if( cQuote && sqlite3_stricmp(zSchema,"temp")==0 ) cQuote = 0; + appendText(&s, zSchema, cQuote); + appendText(&s, ".", 0); + } + cQuote = quoteChar(zName); + appendText(&s, zName, cQuote); + while( sqlite3_step(pStmt)==SQLITE_ROW ){ + const char *zCol = (const char*)sqlite3_column_text(pStmt, 1); + nRow++; + appendText(&s, zDiv, 0); + zDiv = ","; + cQuote = quoteChar(zCol); + appendText(&s, zCol, cQuote); + } + appendText(&s, ")", 0); + sqlite3_finalize(pStmt); + if( nRow==0 ){ + freeText(&s); + s.z = 0; + } + return s.z; +} + +/* +** SQL function: shell_module_schema(X) +** +** Return a fake schema for the table-valued function or eponymous virtual +** table X. +*/ +static void shellModuleSchema( + sqlite3_context *pCtx, + int nVal, + sqlite3_value **apVal +){ + const char *zName = (const char*)sqlite3_value_text(apVal[0]); + char *zFake = shellFakeSchema(sqlite3_context_db_handle(pCtx), 0, zName); + UNUSED_PARAMETER(nVal); + if( zFake ){ + sqlite3_result_text(pCtx, sqlite3_mprintf("/* %s */", zFake), + -1, sqlite3_free); + free(zFake); + } +} + +/* +** SQL function: shell_add_schema(S,X) +** +** Add the schema name X to the CREATE statement in S and return the result. +** Examples: +** +** CREATE TABLE t1(x) -> CREATE TABLE xyz.t1(x); +** +** Also works on +** +** CREATE INDEX +** CREATE UNIQUE INDEX +** CREATE VIEW +** CREATE TRIGGER +** CREATE VIRTUAL TABLE +** +** This UDF is used by the .schema command to insert the schema name of +** attached databases into the middle of the sqlite_schema.sql field. +*/ +static void shellAddSchemaName( + sqlite3_context *pCtx, + int nVal, + sqlite3_value **apVal +){ + static const char *aPrefix[] = { + "TABLE", + "INDEX", + "UNIQUE INDEX", + "VIEW", + "TRIGGER", + "VIRTUAL TABLE" + }; + int i = 0; + const char *zIn = (const char*)sqlite3_value_text(apVal[0]); + const char *zSchema = (const char*)sqlite3_value_text(apVal[1]); + const char *zName = (const char*)sqlite3_value_text(apVal[2]); + sqlite3 *db = sqlite3_context_db_handle(pCtx); + UNUSED_PARAMETER(nVal); + if( zIn!=0 && strncmp(zIn, "CREATE ", 7)==0 ){ + for(i=0; i +#include +#include +#include +#include +#include +#include + +/* +** We may need several defines that should have been in "sys/stat.h". +*/ + +#ifndef S_ISREG +#define S_ISREG(mode) (((mode) & S_IFMT) == S_IFREG) +#endif + +#ifndef S_ISDIR +#define S_ISDIR(mode) (((mode) & S_IFMT) == S_IFDIR) +#endif + +#ifndef S_ISLNK +#define S_ISLNK(mode) (0) +#endif + +/* +** We may need to provide the "mode_t" type. +*/ + +#ifndef MODE_T_DEFINED + #define MODE_T_DEFINED + typedef unsigned short mode_t; +#endif + +/* +** We may need to provide the "ino_t" type. +*/ + +#ifndef INO_T_DEFINED + #define INO_T_DEFINED + typedef unsigned short ino_t; +#endif + +/* +** We need to define "NAME_MAX" if it was not present in "limits.h". +*/ + +#ifndef NAME_MAX +# ifdef FILENAME_MAX +# define NAME_MAX (FILENAME_MAX) +# else +# define NAME_MAX (260) +# endif +#endif + +/* +** We need to define "NULL_INTPTR_T" and "BAD_INTPTR_T". +*/ + +#ifndef NULL_INTPTR_T +# define NULL_INTPTR_T ((intptr_t)(0)) +#endif + +#ifndef BAD_INTPTR_T +# define BAD_INTPTR_T ((intptr_t)(-1)) +#endif + +/* +** We need to provide the necessary structures and related types. +*/ + +#ifndef DIRENT_DEFINED +#define DIRENT_DEFINED +typedef struct DIRENT DIRENT; +typedef DIRENT *LPDIRENT; +struct DIRENT { + ino_t d_ino; /* Sequence number, do not use. */ + unsigned d_attributes; /* Win32 file attributes. */ + char d_name[NAME_MAX + 1]; /* Name within the directory. */ +}; +#endif + +#ifndef DIR_DEFINED +#define DIR_DEFINED +typedef struct DIR DIR; +typedef DIR *LPDIR; +struct DIR { + intptr_t d_handle; /* Value returned by "_findfirst". */ + DIRENT d_first; /* DIRENT constructed based on "_findfirst". */ + DIRENT d_next; /* DIRENT constructed based on "_findnext". */ +}; +#endif + +/* +** Provide a macro, for use by the implementation, to determine if a +** particular directory entry should be skipped over when searching for +** the next directory entry that should be returned by the readdir() or +** readdir_r() functions. +*/ + +#ifndef is_filtered +# define is_filtered(a) ((((a).attrib)&_A_HIDDEN) || (((a).attrib)&_A_SYSTEM)) +#endif + +/* +** Provide the function prototype for the POSIX compatiable getenv() +** function. This function is not thread-safe. +*/ + +extern const char *windirent_getenv(const char *name); + +/* +** Finally, we can provide the function prototypes for the opendir(), +** readdir(), readdir_r(), and closedir() POSIX functions. +*/ + +extern LPDIR opendir(const char *dirname); +extern LPDIRENT readdir(LPDIR dirp); +extern INT readdir_r(LPDIR dirp, LPDIRENT entry, LPDIRENT *result); +extern INT closedir(LPDIR dirp); + +#endif /* defined(WIN32) && defined(_MSC_VER) */ + +/************************* End test_windirent.h ********************/ +/************************* Begin test_windirent.c ******************/ +/* +** 2015 November 30 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +************************************************************************* +** This file contains code to implement most of the opendir() family of +** POSIX functions on Win32 using the MSVCRT. +*/ + +#if defined(_WIN32) && defined(_MSC_VER) +/* #include "test_windirent.h" */ + +/* +** Implementation of the POSIX getenv() function using the Win32 API. +** This function is not thread-safe. +*/ +const char *windirent_getenv( + const char *name +){ + static char value[32768]; /* Maximum length, per MSDN */ + DWORD dwSize = sizeof(value) / sizeof(char); /* Size in chars */ + DWORD dwRet; /* Value returned by GetEnvironmentVariableA() */ + + memset(value, 0, sizeof(value)); + dwRet = GetEnvironmentVariableA(name, value, dwSize); + if( dwRet==0 || dwRet>dwSize ){ + /* + ** The function call to GetEnvironmentVariableA() failed -OR- + ** the buffer is not large enough. Either way, return NULL. + */ + return 0; + }else{ + /* + ** The function call to GetEnvironmentVariableA() succeeded + ** -AND- the buffer contains the entire value. + */ + return value; + } +} + +/* +** Implementation of the POSIX opendir() function using the MSVCRT. +*/ +LPDIR opendir( + const char *dirname +){ + struct _finddata_t data; + LPDIR dirp = (LPDIR)sqlite3_malloc(sizeof(DIR)); + SIZE_T namesize = sizeof(data.name) / sizeof(data.name[0]); + + if( dirp==NULL ) return NULL; + memset(dirp, 0, sizeof(DIR)); + + /* TODO: Remove this if Unix-style root paths are not used. */ + if( sqlite3_stricmp(dirname, "/")==0 ){ + dirname = windirent_getenv("SystemDrive"); + } + + memset(&data, 0, sizeof(struct _finddata_t)); + _snprintf(data.name, namesize, "%s\\*", dirname); + dirp->d_handle = _findfirst(data.name, &data); + + if( dirp->d_handle==BAD_INTPTR_T ){ + closedir(dirp); + return NULL; + } + + /* TODO: Remove this block to allow hidden and/or system files. */ + if( is_filtered(data) ){ +next: + + memset(&data, 0, sizeof(struct _finddata_t)); + if( _findnext(dirp->d_handle, &data)==-1 ){ + closedir(dirp); + return NULL; + } + + /* TODO: Remove this block to allow hidden and/or system files. */ + if( is_filtered(data) ) goto next; + } + + dirp->d_first.d_attributes = data.attrib; + strncpy(dirp->d_first.d_name, data.name, NAME_MAX); + dirp->d_first.d_name[NAME_MAX] = '\0'; + + return dirp; +} + +/* +** Implementation of the POSIX readdir() function using the MSVCRT. +*/ +LPDIRENT readdir( + LPDIR dirp +){ + struct _finddata_t data; + + if( dirp==NULL ) return NULL; + + if( dirp->d_first.d_ino==0 ){ + dirp->d_first.d_ino++; + dirp->d_next.d_ino++; + + return &dirp->d_first; + } + +next: + + memset(&data, 0, sizeof(struct _finddata_t)); + if( _findnext(dirp->d_handle, &data)==-1 ) return NULL; + + /* TODO: Remove this block to allow hidden and/or system files. */ + if( is_filtered(data) ) goto next; + + dirp->d_next.d_ino++; + dirp->d_next.d_attributes = data.attrib; + strncpy(dirp->d_next.d_name, data.name, NAME_MAX); + dirp->d_next.d_name[NAME_MAX] = '\0'; + + return &dirp->d_next; +} + +/* +** Implementation of the POSIX readdir_r() function using the MSVCRT. +*/ +INT readdir_r( + LPDIR dirp, + LPDIRENT entry, + LPDIRENT *result +){ + struct _finddata_t data; + + if( dirp==NULL ) return EBADF; + + if( dirp->d_first.d_ino==0 ){ + dirp->d_first.d_ino++; + dirp->d_next.d_ino++; + + entry->d_ino = dirp->d_first.d_ino; + entry->d_attributes = dirp->d_first.d_attributes; + strncpy(entry->d_name, dirp->d_first.d_name, NAME_MAX); + entry->d_name[NAME_MAX] = '\0'; + + *result = entry; + return 0; + } + +next: + + memset(&data, 0, sizeof(struct _finddata_t)); + if( _findnext(dirp->d_handle, &data)==-1 ){ + *result = NULL; + return ENOENT; + } + + /* TODO: Remove this block to allow hidden and/or system files. */ + if( is_filtered(data) ) goto next; + + entry->d_ino = (ino_t)-1; /* not available */ + entry->d_attributes = data.attrib; + strncpy(entry->d_name, data.name, NAME_MAX); + entry->d_name[NAME_MAX] = '\0'; + + *result = entry; + return 0; +} + +/* +** Implementation of the POSIX closedir() function using the MSVCRT. +*/ +INT closedir( + LPDIR dirp +){ + INT result = 0; + + if( dirp==NULL ) return EINVAL; + + if( dirp->d_handle!=NULL_INTPTR_T && dirp->d_handle!=BAD_INTPTR_T ){ + result = _findclose(dirp->d_handle); + } + + sqlite3_free(dirp); + return result; +} + +#endif /* defined(WIN32) && defined(_MSC_VER) */ + +/************************* End test_windirent.c ********************/ +#define dirent DIRENT +#endif +/************************* Begin ../ext/misc/shathree.c ******************/ +/* +** 2017-03-08 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +****************************************************************************** +** +** This SQLite extension implements functions that compute SHA3 hashes. +** Two SQL functions are implemented: +** +** sha3(X,SIZE) +** sha3_query(Y,SIZE) +** +** The sha3(X) function computes the SHA3 hash of the input X, or NULL if +** X is NULL. +** +** The sha3_query(Y) function evalutes all queries in the SQL statements of Y +** and returns a hash of their results. +** +** The SIZE argument is optional. If omitted, the SHA3-256 hash algorithm +** is used. If SIZE is included it must be one of the integers 224, 256, +** 384, or 512, to determine SHA3 hash variant that is computed. +*/ +/* #include "sqlite3ext.h" */ +SQLITE_EXTENSION_INIT1 +#include +#include +#include + +#ifndef SQLITE_AMALGAMATION +/* typedef sqlite3_uint64 u64; */ +#endif /* SQLITE_AMALGAMATION */ + +/****************************************************************************** +** The Hash Engine +*/ +/* +** Macros to determine whether the machine is big or little endian, +** and whether or not that determination is run-time or compile-time. +** +** For best performance, an attempt is made to guess at the byte-order +** using C-preprocessor macros. If that is unsuccessful, or if +** -DSHA3_BYTEORDER=0 is set, then byte-order is determined +** at run-time. +*/ +#ifndef SHA3_BYTEORDER +# if defined(i386) || defined(__i386__) || defined(_M_IX86) || \ + defined(__x86_64) || defined(__x86_64__) || defined(_M_X64) || \ + defined(_M_AMD64) || defined(_M_ARM) || defined(__x86) || \ + defined(__arm__) +# define SHA3_BYTEORDER 1234 +# elif defined(sparc) || defined(__ppc__) +# define SHA3_BYTEORDER 4321 +# else +# define SHA3_BYTEORDER 0 +# endif +#endif + + +/* +** State structure for a SHA3 hash in progress +*/ +typedef struct SHA3Context SHA3Context; +struct SHA3Context { + union { + u64 s[25]; /* Keccak state. 5x5 lines of 64 bits each */ + unsigned char x[1600]; /* ... or 1600 bytes */ + } u; + unsigned nRate; /* Bytes of input accepted per Keccak iteration */ + unsigned nLoaded; /* Input bytes loaded into u.x[] so far this cycle */ + unsigned ixMask; /* Insert next input into u.x[nLoaded^ixMask]. */ +}; + +/* +** A single step of the Keccak mixing function for a 1600-bit state +*/ +static void KeccakF1600Step(SHA3Context *p){ + int i; + u64 b0, b1, b2, b3, b4; + u64 c0, c1, c2, c3, c4; + u64 d0, d1, d2, d3, d4; + static const u64 RC[] = { + 0x0000000000000001ULL, 0x0000000000008082ULL, + 0x800000000000808aULL, 0x8000000080008000ULL, + 0x000000000000808bULL, 0x0000000080000001ULL, + 0x8000000080008081ULL, 0x8000000000008009ULL, + 0x000000000000008aULL, 0x0000000000000088ULL, + 0x0000000080008009ULL, 0x000000008000000aULL, + 0x000000008000808bULL, 0x800000000000008bULL, + 0x8000000000008089ULL, 0x8000000000008003ULL, + 0x8000000000008002ULL, 0x8000000000000080ULL, + 0x000000000000800aULL, 0x800000008000000aULL, + 0x8000000080008081ULL, 0x8000000000008080ULL, + 0x0000000080000001ULL, 0x8000000080008008ULL + }; +# define a00 (p->u.s[0]) +# define a01 (p->u.s[1]) +# define a02 (p->u.s[2]) +# define a03 (p->u.s[3]) +# define a04 (p->u.s[4]) +# define a10 (p->u.s[5]) +# define a11 (p->u.s[6]) +# define a12 (p->u.s[7]) +# define a13 (p->u.s[8]) +# define a14 (p->u.s[9]) +# define a20 (p->u.s[10]) +# define a21 (p->u.s[11]) +# define a22 (p->u.s[12]) +# define a23 (p->u.s[13]) +# define a24 (p->u.s[14]) +# define a30 (p->u.s[15]) +# define a31 (p->u.s[16]) +# define a32 (p->u.s[17]) +# define a33 (p->u.s[18]) +# define a34 (p->u.s[19]) +# define a40 (p->u.s[20]) +# define a41 (p->u.s[21]) +# define a42 (p->u.s[22]) +# define a43 (p->u.s[23]) +# define a44 (p->u.s[24]) +# define ROL64(a,x) ((a<>(64-x))) + + for(i=0; i<24; i+=4){ + c0 = a00^a10^a20^a30^a40; + c1 = a01^a11^a21^a31^a41; + c2 = a02^a12^a22^a32^a42; + c3 = a03^a13^a23^a33^a43; + c4 = a04^a14^a24^a34^a44; + d0 = c4^ROL64(c1, 1); + d1 = c0^ROL64(c2, 1); + d2 = c1^ROL64(c3, 1); + d3 = c2^ROL64(c4, 1); + d4 = c3^ROL64(c0, 1); + + b0 = (a00^d0); + b1 = ROL64((a11^d1), 44); + b2 = ROL64((a22^d2), 43); + b3 = ROL64((a33^d3), 21); + b4 = ROL64((a44^d4), 14); + a00 = b0 ^((~b1)& b2 ); + a00 ^= RC[i]; + a11 = b1 ^((~b2)& b3 ); + a22 = b2 ^((~b3)& b4 ); + a33 = b3 ^((~b4)& b0 ); + a44 = b4 ^((~b0)& b1 ); + + b2 = ROL64((a20^d0), 3); + b3 = ROL64((a31^d1), 45); + b4 = ROL64((a42^d2), 61); + b0 = ROL64((a03^d3), 28); + b1 = ROL64((a14^d4), 20); + a20 = b0 ^((~b1)& b2 ); + a31 = b1 ^((~b2)& b3 ); + a42 = b2 ^((~b3)& b4 ); + a03 = b3 ^((~b4)& b0 ); + a14 = b4 ^((~b0)& b1 ); + + b4 = ROL64((a40^d0), 18); + b0 = ROL64((a01^d1), 1); + b1 = ROL64((a12^d2), 6); + b2 = ROL64((a23^d3), 25); + b3 = ROL64((a34^d4), 8); + a40 = b0 ^((~b1)& b2 ); + a01 = b1 ^((~b2)& b3 ); + a12 = b2 ^((~b3)& b4 ); + a23 = b3 ^((~b4)& b0 ); + a34 = b4 ^((~b0)& b1 ); + + b1 = ROL64((a10^d0), 36); + b2 = ROL64((a21^d1), 10); + b3 = ROL64((a32^d2), 15); + b4 = ROL64((a43^d3), 56); + b0 = ROL64((a04^d4), 27); + a10 = b0 ^((~b1)& b2 ); + a21 = b1 ^((~b2)& b3 ); + a32 = b2 ^((~b3)& b4 ); + a43 = b3 ^((~b4)& b0 ); + a04 = b4 ^((~b0)& b1 ); + + b3 = ROL64((a30^d0), 41); + b4 = ROL64((a41^d1), 2); + b0 = ROL64((a02^d2), 62); + b1 = ROL64((a13^d3), 55); + b2 = ROL64((a24^d4), 39); + a30 = b0 ^((~b1)& b2 ); + a41 = b1 ^((~b2)& b3 ); + a02 = b2 ^((~b3)& b4 ); + a13 = b3 ^((~b4)& b0 ); + a24 = b4 ^((~b0)& b1 ); + + c0 = a00^a20^a40^a10^a30; + c1 = a11^a31^a01^a21^a41; + c2 = a22^a42^a12^a32^a02; + c3 = a33^a03^a23^a43^a13; + c4 = a44^a14^a34^a04^a24; + d0 = c4^ROL64(c1, 1); + d1 = c0^ROL64(c2, 1); + d2 = c1^ROL64(c3, 1); + d3 = c2^ROL64(c4, 1); + d4 = c3^ROL64(c0, 1); + + b0 = (a00^d0); + b1 = ROL64((a31^d1), 44); + b2 = ROL64((a12^d2), 43); + b3 = ROL64((a43^d3), 21); + b4 = ROL64((a24^d4), 14); + a00 = b0 ^((~b1)& b2 ); + a00 ^= RC[i+1]; + a31 = b1 ^((~b2)& b3 ); + a12 = b2 ^((~b3)& b4 ); + a43 = b3 ^((~b4)& b0 ); + a24 = b4 ^((~b0)& b1 ); + + b2 = ROL64((a40^d0), 3); + b3 = ROL64((a21^d1), 45); + b4 = ROL64((a02^d2), 61); + b0 = ROL64((a33^d3), 28); + b1 = ROL64((a14^d4), 20); + a40 = b0 ^((~b1)& b2 ); + a21 = b1 ^((~b2)& b3 ); + a02 = b2 ^((~b3)& b4 ); + a33 = b3 ^((~b4)& b0 ); + a14 = b4 ^((~b0)& b1 ); + + b4 = ROL64((a30^d0), 18); + b0 = ROL64((a11^d1), 1); + b1 = ROL64((a42^d2), 6); + b2 = ROL64((a23^d3), 25); + b3 = ROL64((a04^d4), 8); + a30 = b0 ^((~b1)& b2 ); + a11 = b1 ^((~b2)& b3 ); + a42 = b2 ^((~b3)& b4 ); + a23 = b3 ^((~b4)& b0 ); + a04 = b4 ^((~b0)& b1 ); + + b1 = ROL64((a20^d0), 36); + b2 = ROL64((a01^d1), 10); + b3 = ROL64((a32^d2), 15); + b4 = ROL64((a13^d3), 56); + b0 = ROL64((a44^d4), 27); + a20 = b0 ^((~b1)& b2 ); + a01 = b1 ^((~b2)& b3 ); + a32 = b2 ^((~b3)& b4 ); + a13 = b3 ^((~b4)& b0 ); + a44 = b4 ^((~b0)& b1 ); + + b3 = ROL64((a10^d0), 41); + b4 = ROL64((a41^d1), 2); + b0 = ROL64((a22^d2), 62); + b1 = ROL64((a03^d3), 55); + b2 = ROL64((a34^d4), 39); + a10 = b0 ^((~b1)& b2 ); + a41 = b1 ^((~b2)& b3 ); + a22 = b2 ^((~b3)& b4 ); + a03 = b3 ^((~b4)& b0 ); + a34 = b4 ^((~b0)& b1 ); + + c0 = a00^a40^a30^a20^a10; + c1 = a31^a21^a11^a01^a41; + c2 = a12^a02^a42^a32^a22; + c3 = a43^a33^a23^a13^a03; + c4 = a24^a14^a04^a44^a34; + d0 = c4^ROL64(c1, 1); + d1 = c0^ROL64(c2, 1); + d2 = c1^ROL64(c3, 1); + d3 = c2^ROL64(c4, 1); + d4 = c3^ROL64(c0, 1); + + b0 = (a00^d0); + b1 = ROL64((a21^d1), 44); + b2 = ROL64((a42^d2), 43); + b3 = ROL64((a13^d3), 21); + b4 = ROL64((a34^d4), 14); + a00 = b0 ^((~b1)& b2 ); + a00 ^= RC[i+2]; + a21 = b1 ^((~b2)& b3 ); + a42 = b2 ^((~b3)& b4 ); + a13 = b3 ^((~b4)& b0 ); + a34 = b4 ^((~b0)& b1 ); + + b2 = ROL64((a30^d0), 3); + b3 = ROL64((a01^d1), 45); + b4 = ROL64((a22^d2), 61); + b0 = ROL64((a43^d3), 28); + b1 = ROL64((a14^d4), 20); + a30 = b0 ^((~b1)& b2 ); + a01 = b1 ^((~b2)& b3 ); + a22 = b2 ^((~b3)& b4 ); + a43 = b3 ^((~b4)& b0 ); + a14 = b4 ^((~b0)& b1 ); + + b4 = ROL64((a10^d0), 18); + b0 = ROL64((a31^d1), 1); + b1 = ROL64((a02^d2), 6); + b2 = ROL64((a23^d3), 25); + b3 = ROL64((a44^d4), 8); + a10 = b0 ^((~b1)& b2 ); + a31 = b1 ^((~b2)& b3 ); + a02 = b2 ^((~b3)& b4 ); + a23 = b3 ^((~b4)& b0 ); + a44 = b4 ^((~b0)& b1 ); + + b1 = ROL64((a40^d0), 36); + b2 = ROL64((a11^d1), 10); + b3 = ROL64((a32^d2), 15); + b4 = ROL64((a03^d3), 56); + b0 = ROL64((a24^d4), 27); + a40 = b0 ^((~b1)& b2 ); + a11 = b1 ^((~b2)& b3 ); + a32 = b2 ^((~b3)& b4 ); + a03 = b3 ^((~b4)& b0 ); + a24 = b4 ^((~b0)& b1 ); + + b3 = ROL64((a20^d0), 41); + b4 = ROL64((a41^d1), 2); + b0 = ROL64((a12^d2), 62); + b1 = ROL64((a33^d3), 55); + b2 = ROL64((a04^d4), 39); + a20 = b0 ^((~b1)& b2 ); + a41 = b1 ^((~b2)& b3 ); + a12 = b2 ^((~b3)& b4 ); + a33 = b3 ^((~b4)& b0 ); + a04 = b4 ^((~b0)& b1 ); + + c0 = a00^a30^a10^a40^a20; + c1 = a21^a01^a31^a11^a41; + c2 = a42^a22^a02^a32^a12; + c3 = a13^a43^a23^a03^a33; + c4 = a34^a14^a44^a24^a04; + d0 = c4^ROL64(c1, 1); + d1 = c0^ROL64(c2, 1); + d2 = c1^ROL64(c3, 1); + d3 = c2^ROL64(c4, 1); + d4 = c3^ROL64(c0, 1); + + b0 = (a00^d0); + b1 = ROL64((a01^d1), 44); + b2 = ROL64((a02^d2), 43); + b3 = ROL64((a03^d3), 21); + b4 = ROL64((a04^d4), 14); + a00 = b0 ^((~b1)& b2 ); + a00 ^= RC[i+3]; + a01 = b1 ^((~b2)& b3 ); + a02 = b2 ^((~b3)& b4 ); + a03 = b3 ^((~b4)& b0 ); + a04 = b4 ^((~b0)& b1 ); + + b2 = ROL64((a10^d0), 3); + b3 = ROL64((a11^d1), 45); + b4 = ROL64((a12^d2), 61); + b0 = ROL64((a13^d3), 28); + b1 = ROL64((a14^d4), 20); + a10 = b0 ^((~b1)& b2 ); + a11 = b1 ^((~b2)& b3 ); + a12 = b2 ^((~b3)& b4 ); + a13 = b3 ^((~b4)& b0 ); + a14 = b4 ^((~b0)& b1 ); + + b4 = ROL64((a20^d0), 18); + b0 = ROL64((a21^d1), 1); + b1 = ROL64((a22^d2), 6); + b2 = ROL64((a23^d3), 25); + b3 = ROL64((a24^d4), 8); + a20 = b0 ^((~b1)& b2 ); + a21 = b1 ^((~b2)& b3 ); + a22 = b2 ^((~b3)& b4 ); + a23 = b3 ^((~b4)& b0 ); + a24 = b4 ^((~b0)& b1 ); + + b1 = ROL64((a30^d0), 36); + b2 = ROL64((a31^d1), 10); + b3 = ROL64((a32^d2), 15); + b4 = ROL64((a33^d3), 56); + b0 = ROL64((a34^d4), 27); + a30 = b0 ^((~b1)& b2 ); + a31 = b1 ^((~b2)& b3 ); + a32 = b2 ^((~b3)& b4 ); + a33 = b3 ^((~b4)& b0 ); + a34 = b4 ^((~b0)& b1 ); + + b3 = ROL64((a40^d0), 41); + b4 = ROL64((a41^d1), 2); + b0 = ROL64((a42^d2), 62); + b1 = ROL64((a43^d3), 55); + b2 = ROL64((a44^d4), 39); + a40 = b0 ^((~b1)& b2 ); + a41 = b1 ^((~b2)& b3 ); + a42 = b2 ^((~b3)& b4 ); + a43 = b3 ^((~b4)& b0 ); + a44 = b4 ^((~b0)& b1 ); + } +} + +/* +** Initialize a new hash. iSize determines the size of the hash +** in bits and should be one of 224, 256, 384, or 512. Or iSize +** can be zero to use the default hash size of 256 bits. +*/ +static void SHA3Init(SHA3Context *p, int iSize){ + memset(p, 0, sizeof(*p)); + if( iSize>=128 && iSize<=512 ){ + p->nRate = (1600 - ((iSize + 31)&~31)*2)/8; + }else{ + p->nRate = (1600 - 2*256)/8; + } +#if SHA3_BYTEORDER==1234 + /* Known to be little-endian at compile-time. No-op */ +#elif SHA3_BYTEORDER==4321 + p->ixMask = 7; /* Big-endian */ +#else + { + static unsigned int one = 1; + if( 1==*(unsigned char*)&one ){ + /* Little endian. No byte swapping. */ + p->ixMask = 0; + }else{ + /* Big endian. Byte swap. */ + p->ixMask = 7; + } + } +#endif +} + +/* +** Make consecutive calls to the SHA3Update function to add new content +** to the hash +*/ +static void SHA3Update( + SHA3Context *p, + const unsigned char *aData, + unsigned int nData +){ + unsigned int i = 0; +#if SHA3_BYTEORDER==1234 + if( (p->nLoaded % 8)==0 && ((aData - (const unsigned char*)0)&7)==0 ){ + for(; i+7u.s[p->nLoaded/8] ^= *(u64*)&aData[i]; + p->nLoaded += 8; + if( p->nLoaded>=p->nRate ){ + KeccakF1600Step(p); + p->nLoaded = 0; + } + } + } +#endif + for(; iu.x[p->nLoaded] ^= aData[i]; +#elif SHA3_BYTEORDER==4321 + p->u.x[p->nLoaded^0x07] ^= aData[i]; +#else + p->u.x[p->nLoaded^p->ixMask] ^= aData[i]; +#endif + p->nLoaded++; + if( p->nLoaded==p->nRate ){ + KeccakF1600Step(p); + p->nLoaded = 0; + } + } +} + +/* +** After all content has been added, invoke SHA3Final() to compute +** the final hash. The function returns a pointer to the binary +** hash value. +*/ +static unsigned char *SHA3Final(SHA3Context *p){ + unsigned int i; + if( p->nLoaded==p->nRate-1 ){ + const unsigned char c1 = 0x86; + SHA3Update(p, &c1, 1); + }else{ + const unsigned char c2 = 0x06; + const unsigned char c3 = 0x80; + SHA3Update(p, &c2, 1); + p->nLoaded = p->nRate - 1; + SHA3Update(p, &c3, 1); + } + for(i=0; inRate; i++){ + p->u.x[i+p->nRate] = p->u.x[i^p->ixMask]; + } + return &p->u.x[p->nRate]; +} +/* End of the hashing logic +*****************************************************************************/ + +/* +** Implementation of the sha3(X,SIZE) function. +** +** Return a BLOB which is the SIZE-bit SHA3 hash of X. The default +** size is 256. If X is a BLOB, it is hashed as is. +** For all other non-NULL types of input, X is converted into a UTF-8 string +** and the string is hashed without the trailing 0x00 terminator. The hash +** of a NULL value is NULL. +*/ +static void sha3Func( + sqlite3_context *context, + int argc, + sqlite3_value **argv +){ + SHA3Context cx; + int eType = sqlite3_value_type(argv[0]); + int nByte = sqlite3_value_bytes(argv[0]); + int iSize; + if( argc==1 ){ + iSize = 256; + }else{ + iSize = sqlite3_value_int(argv[1]); + if( iSize!=224 && iSize!=256 && iSize!=384 && iSize!=512 ){ + sqlite3_result_error(context, "SHA3 size should be one of: 224 256 " + "384 512", -1); + return; + } + } + if( eType==SQLITE_NULL ) return; + SHA3Init(&cx, iSize); + if( eType==SQLITE_BLOB ){ + SHA3Update(&cx, sqlite3_value_blob(argv[0]), nByte); + }else{ + SHA3Update(&cx, sqlite3_value_text(argv[0]), nByte); + } + sqlite3_result_blob(context, SHA3Final(&cx), iSize/8, SQLITE_TRANSIENT); +} + +/* Compute a string using sqlite3_vsnprintf() with a maximum length +** of 50 bytes and add it to the hash. +*/ +static void hash_step_vformat( + SHA3Context *p, /* Add content to this context */ + const char *zFormat, + ... +){ + va_list ap; + int n; + char zBuf[50]; + va_start(ap, zFormat); + sqlite3_vsnprintf(sizeof(zBuf),zBuf,zFormat,ap); + va_end(ap); + n = (int)strlen(zBuf); + SHA3Update(p, (unsigned char*)zBuf, n); +} + +/* +** Implementation of the sha3_query(SQL,SIZE) function. +** +** This function compiles and runs the SQL statement(s) given in the +** argument. The results are hashed using a SIZE-bit SHA3. The default +** size is 256. +** +** The format of the byte stream that is hashed is summarized as follows: +** +** S: +** R +** N +** I +** F +** B: +** T: +** +** is the original SQL text for each statement run and is +** the size of that text. The SQL text is UTF-8. A single R character +** occurs before the start of each row. N means a NULL value. +** I mean an 8-byte little-endian integer . F is a floating point +** number with an 8-byte little-endian IEEE floating point value . +** B means blobs of bytes. T means text rendered as +** bytes of UTF-8. The and values are expressed as an ASCII +** text integers. +** +** For each SQL statement in the X input, there is one S segment. Each +** S segment is followed by zero or more R segments, one for each row in the +** result set. After each R, there are one or more N, I, F, B, or T segments, +** one for each column in the result set. Segments are concatentated directly +** with no delimiters of any kind. +*/ +static void sha3QueryFunc( + sqlite3_context *context, + int argc, + sqlite3_value **argv +){ + sqlite3 *db = sqlite3_context_db_handle(context); + const char *zSql = (const char*)sqlite3_value_text(argv[0]); + sqlite3_stmt *pStmt = 0; + int nCol; /* Number of columns in the result set */ + int i; /* Loop counter */ + int rc; + int n; + const char *z; + SHA3Context cx; + int iSize; + + if( argc==1 ){ + iSize = 256; + }else{ + iSize = sqlite3_value_int(argv[1]); + if( iSize!=224 && iSize!=256 && iSize!=384 && iSize!=512 ){ + sqlite3_result_error(context, "SHA3 size should be one of: 224 256 " + "384 512", -1); + return; + } + } + if( zSql==0 ) return; + SHA3Init(&cx, iSize); + while( zSql[0] ){ + rc = sqlite3_prepare_v2(db, zSql, -1, &pStmt, &zSql); + if( rc ){ + char *zMsg = sqlite3_mprintf("error SQL statement [%s]: %s", + zSql, sqlite3_errmsg(db)); + sqlite3_finalize(pStmt); + sqlite3_result_error(context, zMsg, -1); + sqlite3_free(zMsg); + return; + } + if( !sqlite3_stmt_readonly(pStmt) ){ + char *zMsg = sqlite3_mprintf("non-query: [%s]", sqlite3_sql(pStmt)); + sqlite3_finalize(pStmt); + sqlite3_result_error(context, zMsg, -1); + sqlite3_free(zMsg); + return; + } + nCol = sqlite3_column_count(pStmt); + z = sqlite3_sql(pStmt); + if( z ){ + n = (int)strlen(z); + hash_step_vformat(&cx,"S%d:",n); + SHA3Update(&cx,(unsigned char*)z,n); + } + + /* Compute a hash over the result of the query */ + while( SQLITE_ROW==sqlite3_step(pStmt) ){ + SHA3Update(&cx,(const unsigned char*)"R",1); + for(i=0; i=1; j--){ + x[j] = u & 0xff; + u >>= 8; + } + x[0] = 'I'; + SHA3Update(&cx, x, 9); + break; + } + case SQLITE_FLOAT: { + sqlite3_uint64 u; + int j; + unsigned char x[9]; + double r = sqlite3_column_double(pStmt,i); + memcpy(&u, &r, 8); + for(j=8; j>=1; j--){ + x[j] = u & 0xff; + u >>= 8; + } + x[0] = 'F'; + SHA3Update(&cx,x,9); + break; + } + case SQLITE_TEXT: { + int n2 = sqlite3_column_bytes(pStmt, i); + const unsigned char *z2 = sqlite3_column_text(pStmt, i); + hash_step_vformat(&cx,"T%d:",n2); + SHA3Update(&cx, z2, n2); + break; + } + case SQLITE_BLOB: { + int n2 = sqlite3_column_bytes(pStmt, i); + const unsigned char *z2 = sqlite3_column_blob(pStmt, i); + hash_step_vformat(&cx,"B%d:",n2); + SHA3Update(&cx, z2, n2); + break; + } + } + } + } + sqlite3_finalize(pStmt); + } + sqlite3_result_blob(context, SHA3Final(&cx), iSize/8, SQLITE_TRANSIENT); +} + + +#ifdef _WIN32 + +#endif +int sqlite3_shathree_init( + sqlite3 *db, + char **pzErrMsg, + const sqlite3_api_routines *pApi +){ + int rc = SQLITE_OK; + SQLITE_EXTENSION_INIT2(pApi); + (void)pzErrMsg; /* Unused parameter */ + rc = sqlite3_create_function(db, "sha3", 1, + SQLITE_UTF8 | SQLITE_INNOCUOUS | SQLITE_DETERMINISTIC, + 0, sha3Func, 0, 0); + if( rc==SQLITE_OK ){ + rc = sqlite3_create_function(db, "sha3", 2, + SQLITE_UTF8 | SQLITE_INNOCUOUS | SQLITE_DETERMINISTIC, + 0, sha3Func, 0, 0); + } + if( rc==SQLITE_OK ){ + rc = sqlite3_create_function(db, "sha3_query", 1, + SQLITE_UTF8 | SQLITE_DIRECTONLY, + 0, sha3QueryFunc, 0, 0); + } + if( rc==SQLITE_OK ){ + rc = sqlite3_create_function(db, "sha3_query", 2, + SQLITE_UTF8 | SQLITE_DIRECTONLY, + 0, sha3QueryFunc, 0, 0); + } + return rc; +} + +/************************* End ../ext/misc/shathree.c ********************/ +/************************* Begin ../ext/misc/fileio.c ******************/ +/* +** 2014-06-13 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +****************************************************************************** +** +** This SQLite extension implements SQL functions readfile() and +** writefile(), and eponymous virtual type "fsdir". +** +** WRITEFILE(FILE, DATA [, MODE [, MTIME]]): +** +** If neither of the optional arguments is present, then this UDF +** function writes blob DATA to file FILE. If successful, the number +** of bytes written is returned. If an error occurs, NULL is returned. +** +** If the first option argument - MODE - is present, then it must +** be passed an integer value that corresponds to a POSIX mode +** value (file type + permissions, as returned in the stat.st_mode +** field by the stat() system call). Three types of files may +** be written/created: +** +** regular files: (mode & 0170000)==0100000 +** symbolic links: (mode & 0170000)==0120000 +** directories: (mode & 0170000)==0040000 +** +** For a directory, the DATA is ignored. For a symbolic link, it is +** interpreted as text and used as the target of the link. For a +** regular file, it is interpreted as a blob and written into the +** named file. Regardless of the type of file, its permissions are +** set to (mode & 0777) before returning. +** +** If the optional MTIME argument is present, then it is interpreted +** as an integer - the number of seconds since the unix epoch. The +** modification-time of the target file is set to this value before +** returning. +** +** If three or more arguments are passed to this function and an +** error is encountered, an exception is raised. +** +** READFILE(FILE): +** +** Read and return the contents of file FILE (type blob) from disk. +** +** FSDIR: +** +** Used as follows: +** +** SELECT * FROM fsdir($path [, $dir]); +** +** Parameter $path is an absolute or relative pathname. If the file that it +** refers to does not exist, it is an error. If the path refers to a regular +** file or symbolic link, it returns a single row. Or, if the path refers +** to a directory, it returns one row for the directory, and one row for each +** file within the hierarchy rooted at $path. +** +** Each row has the following columns: +** +** name: Path to file or directory (text value). +** mode: Value of stat.st_mode for directory entry (an integer). +** mtime: Value of stat.st_mtime for directory entry (an integer). +** data: For a regular file, a blob containing the file data. For a +** symlink, a text value containing the text of the link. For a +** directory, NULL. +** +** If a non-NULL value is specified for the optional $dir parameter and +** $path is a relative path, then $path is interpreted relative to $dir. +** And the paths returned in the "name" column of the table are also +** relative to directory $dir. +** +** Notes on building this extension for Windows: +** Unless linked statically with the SQLite library, a preprocessor +** symbol, FILEIO_WIN32_DLL, must be #define'd to create a stand-alone +** DLL form of this extension for WIN32. See its use below for details. +*/ +/* #include "sqlite3ext.h" */ +SQLITE_EXTENSION_INIT1 +#include +#include +#include + +#include +#include +#include +#if !defined(_WIN32) && !defined(WIN32) +# include +# include +# include +# include +#else +# include "windows.h" +# include +# include +/* # include "test_windirent.h" */ +# define dirent DIRENT +# ifndef chmod +# define chmod _chmod +# endif +# ifndef stat +# define stat _stat +# endif +# define mkdir(path,mode) _mkdir(path) +# define lstat(path,buf) stat(path,buf) +#endif +#include +#include + + +/* +** Structure of the fsdir() table-valued function +*/ + /* 0 1 2 3 4 5 */ +#define FSDIR_SCHEMA "(name,mode,mtime,data,path HIDDEN,dir HIDDEN)" +#define FSDIR_COLUMN_NAME 0 /* Name of the file */ +#define FSDIR_COLUMN_MODE 1 /* Access mode */ +#define FSDIR_COLUMN_MTIME 2 /* Last modification time */ +#define FSDIR_COLUMN_DATA 3 /* File content */ +#define FSDIR_COLUMN_PATH 4 /* Path to top of search */ +#define FSDIR_COLUMN_DIR 5 /* Path is relative to this directory */ + + +/* +** Set the result stored by context ctx to a blob containing the +** contents of file zName. Or, leave the result unchanged (NULL) +** if the file does not exist or is unreadable. +** +** If the file exceeds the SQLite blob size limit, through an +** SQLITE_TOOBIG error. +** +** Throw an SQLITE_IOERR if there are difficulties pulling the file +** off of disk. +*/ +static void readFileContents(sqlite3_context *ctx, const char *zName){ + FILE *in; + sqlite3_int64 nIn; + void *pBuf; + sqlite3 *db; + int mxBlob; + + in = fopen(zName, "rb"); + if( in==0 ){ + /* File does not exist or is unreadable. Leave the result set to NULL. */ + return; + } + fseek(in, 0, SEEK_END); + nIn = ftell(in); + rewind(in); + db = sqlite3_context_db_handle(ctx); + mxBlob = sqlite3_limit(db, SQLITE_LIMIT_LENGTH, -1); + if( nIn>mxBlob ){ + sqlite3_result_error_code(ctx, SQLITE_TOOBIG); + fclose(in); + return; + } + pBuf = sqlite3_malloc64( nIn ? nIn : 1 ); + if( pBuf==0 ){ + sqlite3_result_error_nomem(ctx); + fclose(in); + return; + } + if( nIn==(sqlite3_int64)fread(pBuf, 1, (size_t)nIn, in) ){ + sqlite3_result_blob64(ctx, pBuf, nIn, sqlite3_free); + }else{ + sqlite3_result_error_code(ctx, SQLITE_IOERR); + sqlite3_free(pBuf); + } + fclose(in); +} + +/* +** Implementation of the "readfile(X)" SQL function. The entire content +** of the file named X is read and returned as a BLOB. NULL is returned +** if the file does not exist or is unreadable. +*/ +static void readfileFunc( + sqlite3_context *context, + int argc, + sqlite3_value **argv +){ + const char *zName; + (void)(argc); /* Unused parameter */ + zName = (const char*)sqlite3_value_text(argv[0]); + if( zName==0 ) return; + readFileContents(context, zName); +} + +/* +** Set the error message contained in context ctx to the results of +** vprintf(zFmt, ...). +*/ +static void ctxErrorMsg(sqlite3_context *ctx, const char *zFmt, ...){ + char *zMsg = 0; + va_list ap; + va_start(ap, zFmt); + zMsg = sqlite3_vmprintf(zFmt, ap); + sqlite3_result_error(ctx, zMsg, -1); + sqlite3_free(zMsg); + va_end(ap); +} + +#if defined(_WIN32) +/* +** This function is designed to convert a Win32 FILETIME structure into the +** number of seconds since the Unix Epoch (1970-01-01 00:00:00 UTC). +*/ +static sqlite3_uint64 fileTimeToUnixTime( + LPFILETIME pFileTime +){ + SYSTEMTIME epochSystemTime; + ULARGE_INTEGER epochIntervals; + FILETIME epochFileTime; + ULARGE_INTEGER fileIntervals; + + memset(&epochSystemTime, 0, sizeof(SYSTEMTIME)); + epochSystemTime.wYear = 1970; + epochSystemTime.wMonth = 1; + epochSystemTime.wDay = 1; + SystemTimeToFileTime(&epochSystemTime, &epochFileTime); + epochIntervals.LowPart = epochFileTime.dwLowDateTime; + epochIntervals.HighPart = epochFileTime.dwHighDateTime; + + fileIntervals.LowPart = pFileTime->dwLowDateTime; + fileIntervals.HighPart = pFileTime->dwHighDateTime; + + return (fileIntervals.QuadPart - epochIntervals.QuadPart) / 10000000; +} + + +#if defined(FILEIO_WIN32_DLL) && (defined(_WIN32) || defined(WIN32)) +# /* To allow a standalone DLL, use this next replacement function: */ +# undef sqlite3_win32_utf8_to_unicode +# define sqlite3_win32_utf8_to_unicode utf8_to_utf16 +# +LPWSTR utf8_to_utf16(const char *z){ + int nAllot = MultiByteToWideChar(CP_UTF8, 0, z, -1, NULL, 0); + LPWSTR rv = sqlite3_malloc(nAllot * sizeof(WCHAR)); + if( rv!=0 && 0 < MultiByteToWideChar(CP_UTF8, 0, z, -1, rv, nAllot) ) + return rv; + sqlite3_free(rv); + return 0; +} +#endif + +/* +** This function attempts to normalize the time values found in the stat() +** buffer to UTC. This is necessary on Win32, where the runtime library +** appears to return these values as local times. +*/ +static void statTimesToUtc( + const char *zPath, + struct stat *pStatBuf +){ + HANDLE hFindFile; + WIN32_FIND_DATAW fd; + LPWSTR zUnicodeName; + extern LPWSTR sqlite3_win32_utf8_to_unicode(const char*); + zUnicodeName = sqlite3_win32_utf8_to_unicode(zPath); + if( zUnicodeName ){ + memset(&fd, 0, sizeof(WIN32_FIND_DATAW)); + hFindFile = FindFirstFileW(zUnicodeName, &fd); + if( hFindFile!=NULL ){ + pStatBuf->st_ctime = (time_t)fileTimeToUnixTime(&fd.ftCreationTime); + pStatBuf->st_atime = (time_t)fileTimeToUnixTime(&fd.ftLastAccessTime); + pStatBuf->st_mtime = (time_t)fileTimeToUnixTime(&fd.ftLastWriteTime); + FindClose(hFindFile); + } + sqlite3_free(zUnicodeName); + } +} +#endif + +/* +** This function is used in place of stat(). On Windows, special handling +** is required in order for the included time to be returned as UTC. On all +** other systems, this function simply calls stat(). +*/ +static int fileStat( + const char *zPath, + struct stat *pStatBuf +){ +#if defined(_WIN32) + int rc = stat(zPath, pStatBuf); + if( rc==0 ) statTimesToUtc(zPath, pStatBuf); + return rc; +#else + return stat(zPath, pStatBuf); +#endif +} + +/* +** This function is used in place of lstat(). On Windows, special handling +** is required in order for the included time to be returned as UTC. On all +** other systems, this function simply calls lstat(). +*/ +static int fileLinkStat( + const char *zPath, + struct stat *pStatBuf +){ +#if defined(_WIN32) + int rc = lstat(zPath, pStatBuf); + if( rc==0 ) statTimesToUtc(zPath, pStatBuf); + return rc; +#else + return lstat(zPath, pStatBuf); +#endif +} + +/* +** Argument zFile is the name of a file that will be created and/or written +** by SQL function writefile(). This function ensures that the directory +** zFile will be written to exists, creating it if required. The permissions +** for any path components created by this function are set in accordance +** with the current umask. +** +** If an OOM condition is encountered, SQLITE_NOMEM is returned. Otherwise, +** SQLITE_OK is returned if the directory is successfully created, or +** SQLITE_ERROR otherwise. +*/ +static int makeDirectory( + const char *zFile +){ + char *zCopy = sqlite3_mprintf("%s", zFile); + int rc = SQLITE_OK; + + if( zCopy==0 ){ + rc = SQLITE_NOMEM; + }else{ + int nCopy = (int)strlen(zCopy); + int i = 1; + + while( rc==SQLITE_OK ){ + struct stat sStat; + int rc2; + + for(; zCopy[i]!='/' && i=0 ){ +#if defined(_WIN32) +#if !SQLITE_OS_WINRT + /* Windows */ + FILETIME lastAccess; + FILETIME lastWrite; + SYSTEMTIME currentTime; + LONGLONG intervals; + HANDLE hFile; + LPWSTR zUnicodeName; + extern LPWSTR sqlite3_win32_utf8_to_unicode(const char*); + + GetSystemTime(¤tTime); + SystemTimeToFileTime(¤tTime, &lastAccess); + intervals = Int32x32To64(mtime, 10000000) + 116444736000000000; + lastWrite.dwLowDateTime = (DWORD)intervals; + lastWrite.dwHighDateTime = intervals >> 32; + zUnicodeName = sqlite3_win32_utf8_to_unicode(zFile); + if( zUnicodeName==0 ){ + return 1; + } + hFile = CreateFileW( + zUnicodeName, FILE_WRITE_ATTRIBUTES, 0, NULL, OPEN_EXISTING, + FILE_FLAG_BACKUP_SEMANTICS, NULL + ); + sqlite3_free(zUnicodeName); + if( hFile!=INVALID_HANDLE_VALUE ){ + BOOL bResult = SetFileTime(hFile, NULL, &lastAccess, &lastWrite); + CloseHandle(hFile); + return !bResult; + }else{ + return 1; + } +#endif +#elif defined(AT_FDCWD) && 0 /* utimensat() is not universally available */ + /* Recent unix */ + struct timespec times[2]; + times[0].tv_nsec = times[1].tv_nsec = 0; + times[0].tv_sec = time(0); + times[1].tv_sec = mtime; + if( utimensat(AT_FDCWD, zFile, times, AT_SYMLINK_NOFOLLOW) ){ + return 1; + } +#else + /* Legacy unix */ + struct timeval times[2]; + times[0].tv_usec = times[1].tv_usec = 0; + times[0].tv_sec = time(0); + times[1].tv_sec = mtime; + if( utimes(zFile, times) ){ + return 1; + } +#endif + } + + return 0; +} + +/* +** Implementation of the "writefile(W,X[,Y[,Z]]])" SQL function. +** Refer to header comments at the top of this file for details. +*/ +static void writefileFunc( + sqlite3_context *context, + int argc, + sqlite3_value **argv +){ + const char *zFile; + mode_t mode = 0; + int res; + sqlite3_int64 mtime = -1; + + if( argc<2 || argc>4 ){ + sqlite3_result_error(context, + "wrong number of arguments to function writefile()", -1 + ); + return; + } + + zFile = (const char*)sqlite3_value_text(argv[0]); + if( zFile==0 ) return; + if( argc>=3 ){ + mode = (mode_t)sqlite3_value_int(argv[2]); + } + if( argc==4 ){ + mtime = sqlite3_value_int64(argv[3]); + } + + res = writeFile(context, zFile, argv[1], mode, mtime); + if( res==1 && errno==ENOENT ){ + if( makeDirectory(zFile)==SQLITE_OK ){ + res = writeFile(context, zFile, argv[1], mode, mtime); + } + } + + if( argc>2 && res!=0 ){ + if( S_ISLNK(mode) ){ + ctxErrorMsg(context, "failed to create symlink: %s", zFile); + }else if( S_ISDIR(mode) ){ + ctxErrorMsg(context, "failed to create directory: %s", zFile); + }else{ + ctxErrorMsg(context, "failed to write file: %s", zFile); + } + } +} + +/* +** SQL function: lsmode(MODE) +** +** Given a numberic st_mode from stat(), convert it into a human-readable +** text string in the style of "ls -l". +*/ +static void lsModeFunc( + sqlite3_context *context, + int argc, + sqlite3_value **argv +){ + int i; + int iMode = sqlite3_value_int(argv[0]); + char z[16]; + (void)argc; + if( S_ISLNK(iMode) ){ + z[0] = 'l'; + }else if( S_ISREG(iMode) ){ + z[0] = '-'; + }else if( S_ISDIR(iMode) ){ + z[0] = 'd'; + }else{ + z[0] = '?'; + } + for(i=0; i<3; i++){ + int m = (iMode >> ((2-i)*3)); + char *a = &z[1 + i*3]; + a[0] = (m & 0x4) ? 'r' : '-'; + a[1] = (m & 0x2) ? 'w' : '-'; + a[2] = (m & 0x1) ? 'x' : '-'; + } + z[10] = '\0'; + sqlite3_result_text(context, z, -1, SQLITE_TRANSIENT); +} + +#ifndef SQLITE_OMIT_VIRTUALTABLE + +/* +** Cursor type for recursively iterating through a directory structure. +*/ +typedef struct fsdir_cursor fsdir_cursor; +typedef struct FsdirLevel FsdirLevel; + +struct FsdirLevel { + DIR *pDir; /* From opendir() */ + char *zDir; /* Name of directory (nul-terminated) */ +}; + +struct fsdir_cursor { + sqlite3_vtab_cursor base; /* Base class - must be first */ + + int nLvl; /* Number of entries in aLvl[] array */ + int iLvl; /* Index of current entry */ + FsdirLevel *aLvl; /* Hierarchy of directories being traversed */ + + const char *zBase; + int nBase; + + struct stat sStat; /* Current lstat() results */ + char *zPath; /* Path to current entry */ + sqlite3_int64 iRowid; /* Current rowid */ +}; + +typedef struct fsdir_tab fsdir_tab; +struct fsdir_tab { + sqlite3_vtab base; /* Base class - must be first */ +}; + +/* +** Construct a new fsdir virtual table object. +*/ +static int fsdirConnect( + sqlite3 *db, + void *pAux, + int argc, const char *const*argv, + sqlite3_vtab **ppVtab, + char **pzErr +){ + fsdir_tab *pNew = 0; + int rc; + (void)pAux; + (void)argc; + (void)argv; + (void)pzErr; + rc = sqlite3_declare_vtab(db, "CREATE TABLE x" FSDIR_SCHEMA); + if( rc==SQLITE_OK ){ + pNew = (fsdir_tab*)sqlite3_malloc( sizeof(*pNew) ); + if( pNew==0 ) return SQLITE_NOMEM; + memset(pNew, 0, sizeof(*pNew)); + sqlite3_vtab_config(db, SQLITE_VTAB_DIRECTONLY); + } + *ppVtab = (sqlite3_vtab*)pNew; + return rc; +} + +/* +** This method is the destructor for fsdir vtab objects. +*/ +static int fsdirDisconnect(sqlite3_vtab *pVtab){ + sqlite3_free(pVtab); + return SQLITE_OK; +} + +/* +** Constructor for a new fsdir_cursor object. +*/ +static int fsdirOpen(sqlite3_vtab *p, sqlite3_vtab_cursor **ppCursor){ + fsdir_cursor *pCur; + (void)p; + pCur = sqlite3_malloc( sizeof(*pCur) ); + if( pCur==0 ) return SQLITE_NOMEM; + memset(pCur, 0, sizeof(*pCur)); + pCur->iLvl = -1; + *ppCursor = &pCur->base; + return SQLITE_OK; +} + +/* +** Reset a cursor back to the state it was in when first returned +** by fsdirOpen(). +*/ +static void fsdirResetCursor(fsdir_cursor *pCur){ + int i; + for(i=0; i<=pCur->iLvl; i++){ + FsdirLevel *pLvl = &pCur->aLvl[i]; + if( pLvl->pDir ) closedir(pLvl->pDir); + sqlite3_free(pLvl->zDir); + } + sqlite3_free(pCur->zPath); + sqlite3_free(pCur->aLvl); + pCur->aLvl = 0; + pCur->zPath = 0; + pCur->zBase = 0; + pCur->nBase = 0; + pCur->nLvl = 0; + pCur->iLvl = -1; + pCur->iRowid = 1; +} + +/* +** Destructor for an fsdir_cursor. +*/ +static int fsdirClose(sqlite3_vtab_cursor *cur){ + fsdir_cursor *pCur = (fsdir_cursor*)cur; + + fsdirResetCursor(pCur); + sqlite3_free(pCur); + return SQLITE_OK; +} + +/* +** Set the error message for the virtual table associated with cursor +** pCur to the results of vprintf(zFmt, ...). +*/ +static void fsdirSetErrmsg(fsdir_cursor *pCur, const char *zFmt, ...){ + va_list ap; + va_start(ap, zFmt); + pCur->base.pVtab->zErrMsg = sqlite3_vmprintf(zFmt, ap); + va_end(ap); +} + + +/* +** Advance an fsdir_cursor to its next row of output. +*/ +static int fsdirNext(sqlite3_vtab_cursor *cur){ + fsdir_cursor *pCur = (fsdir_cursor*)cur; + mode_t m = pCur->sStat.st_mode; + + pCur->iRowid++; + if( S_ISDIR(m) ){ + /* Descend into this directory */ + int iNew = pCur->iLvl + 1; + FsdirLevel *pLvl; + if( iNew>=pCur->nLvl ){ + int nNew = iNew+1; + sqlite3_int64 nByte = nNew*sizeof(FsdirLevel); + FsdirLevel *aNew = (FsdirLevel*)sqlite3_realloc64(pCur->aLvl, nByte); + if( aNew==0 ) return SQLITE_NOMEM; + memset(&aNew[pCur->nLvl], 0, sizeof(FsdirLevel)*(nNew-pCur->nLvl)); + pCur->aLvl = aNew; + pCur->nLvl = nNew; + } + pCur->iLvl = iNew; + pLvl = &pCur->aLvl[iNew]; + + pLvl->zDir = pCur->zPath; + pCur->zPath = 0; + pLvl->pDir = opendir(pLvl->zDir); + if( pLvl->pDir==0 ){ + fsdirSetErrmsg(pCur, "cannot read directory: %s", pCur->zPath); + return SQLITE_ERROR; + } + } + + while( pCur->iLvl>=0 ){ + FsdirLevel *pLvl = &pCur->aLvl[pCur->iLvl]; + struct dirent *pEntry = readdir(pLvl->pDir); + if( pEntry ){ + if( pEntry->d_name[0]=='.' ){ + if( pEntry->d_name[1]=='.' && pEntry->d_name[2]=='\0' ) continue; + if( pEntry->d_name[1]=='\0' ) continue; + } + sqlite3_free(pCur->zPath); + pCur->zPath = sqlite3_mprintf("%s/%s", pLvl->zDir, pEntry->d_name); + if( pCur->zPath==0 ) return SQLITE_NOMEM; + if( fileLinkStat(pCur->zPath, &pCur->sStat) ){ + fsdirSetErrmsg(pCur, "cannot stat file: %s", pCur->zPath); + return SQLITE_ERROR; + } + return SQLITE_OK; + } + closedir(pLvl->pDir); + sqlite3_free(pLvl->zDir); + pLvl->pDir = 0; + pLvl->zDir = 0; + pCur->iLvl--; + } + + /* EOF */ + sqlite3_free(pCur->zPath); + pCur->zPath = 0; + return SQLITE_OK; +} + +/* +** Return values of columns for the row at which the series_cursor +** is currently pointing. +*/ +static int fsdirColumn( + sqlite3_vtab_cursor *cur, /* The cursor */ + sqlite3_context *ctx, /* First argument to sqlite3_result_...() */ + int i /* Which column to return */ +){ + fsdir_cursor *pCur = (fsdir_cursor*)cur; + switch( i ){ + case FSDIR_COLUMN_NAME: { + sqlite3_result_text(ctx, &pCur->zPath[pCur->nBase], -1, SQLITE_TRANSIENT); + break; + } + + case FSDIR_COLUMN_MODE: + sqlite3_result_int64(ctx, pCur->sStat.st_mode); + break; + + case FSDIR_COLUMN_MTIME: + sqlite3_result_int64(ctx, pCur->sStat.st_mtime); + break; + + case FSDIR_COLUMN_DATA: { + mode_t m = pCur->sStat.st_mode; + if( S_ISDIR(m) ){ + sqlite3_result_null(ctx); +#if !defined(_WIN32) && !defined(WIN32) + }else if( S_ISLNK(m) ){ + char aStatic[64]; + char *aBuf = aStatic; + sqlite3_int64 nBuf = 64; + int n; + + while( 1 ){ + n = readlink(pCur->zPath, aBuf, nBuf); + if( nzPath); + } + } + case FSDIR_COLUMN_PATH: + default: { + /* The FSDIR_COLUMN_PATH and FSDIR_COLUMN_DIR are input parameters. + ** always return their values as NULL */ + break; + } + } + return SQLITE_OK; +} + +/* +** Return the rowid for the current row. In this implementation, the +** first row returned is assigned rowid value 1, and each subsequent +** row a value 1 more than that of the previous. +*/ +static int fsdirRowid(sqlite3_vtab_cursor *cur, sqlite_int64 *pRowid){ + fsdir_cursor *pCur = (fsdir_cursor*)cur; + *pRowid = pCur->iRowid; + return SQLITE_OK; +} + +/* +** Return TRUE if the cursor has been moved off of the last +** row of output. +*/ +static int fsdirEof(sqlite3_vtab_cursor *cur){ + fsdir_cursor *pCur = (fsdir_cursor*)cur; + return (pCur->zPath==0); +} + +/* +** xFilter callback. +** +** idxNum==1 PATH parameter only +** idxNum==2 Both PATH and DIR supplied +*/ +static int fsdirFilter( + sqlite3_vtab_cursor *cur, + int idxNum, const char *idxStr, + int argc, sqlite3_value **argv +){ + const char *zDir = 0; + fsdir_cursor *pCur = (fsdir_cursor*)cur; + (void)idxStr; + fsdirResetCursor(pCur); + + if( idxNum==0 ){ + fsdirSetErrmsg(pCur, "table function fsdir requires an argument"); + return SQLITE_ERROR; + } + + assert( argc==idxNum && (argc==1 || argc==2) ); + zDir = (const char*)sqlite3_value_text(argv[0]); + if( zDir==0 ){ + fsdirSetErrmsg(pCur, "table function fsdir requires a non-NULL argument"); + return SQLITE_ERROR; + } + if( argc==2 ){ + pCur->zBase = (const char*)sqlite3_value_text(argv[1]); + } + if( pCur->zBase ){ + pCur->nBase = (int)strlen(pCur->zBase)+1; + pCur->zPath = sqlite3_mprintf("%s/%s", pCur->zBase, zDir); + }else{ + pCur->zPath = sqlite3_mprintf("%s", zDir); + } + + if( pCur->zPath==0 ){ + return SQLITE_NOMEM; + } + if( fileLinkStat(pCur->zPath, &pCur->sStat) ){ + fsdirSetErrmsg(pCur, "cannot stat file: %s", pCur->zPath); + return SQLITE_ERROR; + } + + return SQLITE_OK; +} + +/* +** SQLite will invoke this method one or more times while planning a query +** that uses the generate_series virtual table. This routine needs to create +** a query plan for each invocation and compute an estimated cost for that +** plan. +** +** In this implementation idxNum is used to represent the +** query plan. idxStr is unused. +** +** The query plan is represented by values of idxNum: +** +** (1) The path value is supplied by argv[0] +** (2) Path is in argv[0] and dir is in argv[1] +*/ +static int fsdirBestIndex( + sqlite3_vtab *tab, + sqlite3_index_info *pIdxInfo +){ + int i; /* Loop over constraints */ + int idxPath = -1; /* Index in pIdxInfo->aConstraint of PATH= */ + int idxDir = -1; /* Index in pIdxInfo->aConstraint of DIR= */ + int seenPath = 0; /* True if an unusable PATH= constraint is seen */ + int seenDir = 0; /* True if an unusable DIR= constraint is seen */ + const struct sqlite3_index_constraint *pConstraint; + + (void)tab; + pConstraint = pIdxInfo->aConstraint; + for(i=0; inConstraint; i++, pConstraint++){ + if( pConstraint->op!=SQLITE_INDEX_CONSTRAINT_EQ ) continue; + switch( pConstraint->iColumn ){ + case FSDIR_COLUMN_PATH: { + if( pConstraint->usable ){ + idxPath = i; + seenPath = 0; + }else if( idxPath<0 ){ + seenPath = 1; + } + break; + } + case FSDIR_COLUMN_DIR: { + if( pConstraint->usable ){ + idxDir = i; + seenDir = 0; + }else if( idxDir<0 ){ + seenDir = 1; + } + break; + } + } + } + if( seenPath || seenDir ){ + /* If input parameters are unusable, disallow this plan */ + return SQLITE_CONSTRAINT; + } + + if( idxPath<0 ){ + pIdxInfo->idxNum = 0; + /* The pIdxInfo->estimatedCost should have been initialized to a huge + ** number. Leave it unchanged. */ + pIdxInfo->estimatedRows = 0x7fffffff; + }else{ + pIdxInfo->aConstraintUsage[idxPath].omit = 1; + pIdxInfo->aConstraintUsage[idxPath].argvIndex = 1; + if( idxDir>=0 ){ + pIdxInfo->aConstraintUsage[idxDir].omit = 1; + pIdxInfo->aConstraintUsage[idxDir].argvIndex = 2; + pIdxInfo->idxNum = 2; + pIdxInfo->estimatedCost = 10.0; + }else{ + pIdxInfo->idxNum = 1; + pIdxInfo->estimatedCost = 100.0; + } + } + + return SQLITE_OK; +} + +/* +** Register the "fsdir" virtual table. +*/ +static int fsdirRegister(sqlite3 *db){ + static sqlite3_module fsdirModule = { + 0, /* iVersion */ + 0, /* xCreate */ + fsdirConnect, /* xConnect */ + fsdirBestIndex, /* xBestIndex */ + fsdirDisconnect, /* xDisconnect */ + 0, /* xDestroy */ + fsdirOpen, /* xOpen - open a cursor */ + fsdirClose, /* xClose - close a cursor */ + fsdirFilter, /* xFilter - configure scan constraints */ + fsdirNext, /* xNext - advance a cursor */ + fsdirEof, /* xEof - check for end of scan */ + fsdirColumn, /* xColumn - read data */ + fsdirRowid, /* xRowid - read data */ + 0, /* xUpdate */ + 0, /* xBegin */ + 0, /* xSync */ + 0, /* xCommit */ + 0, /* xRollback */ + 0, /* xFindMethod */ + 0, /* xRename */ + 0, /* xSavepoint */ + 0, /* xRelease */ + 0, /* xRollbackTo */ + 0, /* xShadowName */ + }; + + int rc = sqlite3_create_module(db, "fsdir", &fsdirModule, 0); + return rc; +} +#else /* SQLITE_OMIT_VIRTUALTABLE */ +# define fsdirRegister(x) SQLITE_OK +#endif + +#ifdef _WIN32 + +#endif +int sqlite3_fileio_init( + sqlite3 *db, + char **pzErrMsg, + const sqlite3_api_routines *pApi +){ + int rc = SQLITE_OK; + SQLITE_EXTENSION_INIT2(pApi); + (void)pzErrMsg; /* Unused parameter */ + rc = sqlite3_create_function(db, "readfile", 1, + SQLITE_UTF8|SQLITE_DIRECTONLY, 0, + readfileFunc, 0, 0); + if( rc==SQLITE_OK ){ + rc = sqlite3_create_function(db, "writefile", -1, + SQLITE_UTF8|SQLITE_DIRECTONLY, 0, + writefileFunc, 0, 0); + } + if( rc==SQLITE_OK ){ + rc = sqlite3_create_function(db, "lsmode", 1, SQLITE_UTF8, 0, + lsModeFunc, 0, 0); + } + if( rc==SQLITE_OK ){ + rc = fsdirRegister(db); + } + return rc; +} + +#if defined(FILEIO_WIN32_DLL) && (defined(_WIN32) || defined(WIN32)) +/* To allow a standalone DLL, make test_windirent.c use the same + * redefined SQLite API calls as the above extension code does. + * Just pull in this .c to accomplish this. As a beneficial side + * effect, this extension becomes a single translation unit. */ +# include "test_windirent.c" +#endif + +/************************* End ../ext/misc/fileio.c ********************/ +/************************* Begin ../ext/misc/completion.c ******************/ +/* +** 2017-07-10 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +************************************************************************* +** +** This file implements an eponymous virtual table that returns suggested +** completions for a partial SQL input. +** +** Suggested usage: +** +** SELECT DISTINCT candidate COLLATE nocase +** FROM completion($prefix,$wholeline) +** ORDER BY 1; +** +** The two query parameters are optional. $prefix is the text of the +** current word being typed and that is to be completed. $wholeline is +** the complete input line, used for context. +** +** The raw completion() table might return the same candidate multiple +** times, for example if the same column name is used to two or more +** tables. And the candidates are returned in an arbitrary order. Hence, +** the DISTINCT and ORDER BY are recommended. +** +** This virtual table operates at the speed of human typing, and so there +** is no attempt to make it fast. Even a slow implementation will be much +** faster than any human can type. +** +*/ +/* #include "sqlite3ext.h" */ +SQLITE_EXTENSION_INIT1 +#include +#include +#include + +#ifndef SQLITE_OMIT_VIRTUALTABLE + +/* completion_vtab is a subclass of sqlite3_vtab which will +** serve as the underlying representation of a completion virtual table +*/ +typedef struct completion_vtab completion_vtab; +struct completion_vtab { + sqlite3_vtab base; /* Base class - must be first */ + sqlite3 *db; /* Database connection for this completion vtab */ +}; + +/* completion_cursor is a subclass of sqlite3_vtab_cursor which will +** serve as the underlying representation of a cursor that scans +** over rows of the result +*/ +typedef struct completion_cursor completion_cursor; +struct completion_cursor { + sqlite3_vtab_cursor base; /* Base class - must be first */ + sqlite3 *db; /* Database connection for this cursor */ + int nPrefix, nLine; /* Number of bytes in zPrefix and zLine */ + char *zPrefix; /* The prefix for the word we want to complete */ + char *zLine; /* The whole that we want to complete */ + const char *zCurrentRow; /* Current output row */ + int szRow; /* Length of the zCurrentRow string */ + sqlite3_stmt *pStmt; /* Current statement */ + sqlite3_int64 iRowid; /* The rowid */ + int ePhase; /* Current phase */ + int j; /* inter-phase counter */ +}; + +/* Values for ePhase: +*/ +#define COMPLETION_FIRST_PHASE 1 +#define COMPLETION_KEYWORDS 1 +#define COMPLETION_PRAGMAS 2 +#define COMPLETION_FUNCTIONS 3 +#define COMPLETION_COLLATIONS 4 +#define COMPLETION_INDEXES 5 +#define COMPLETION_TRIGGERS 6 +#define COMPLETION_DATABASES 7 +#define COMPLETION_TABLES 8 /* Also VIEWs and TRIGGERs */ +#define COMPLETION_COLUMNS 9 +#define COMPLETION_MODULES 10 +#define COMPLETION_EOF 11 + +/* +** The completionConnect() method is invoked to create a new +** completion_vtab that describes the completion virtual table. +** +** Think of this routine as the constructor for completion_vtab objects. +** +** All this routine needs to do is: +** +** (1) Allocate the completion_vtab object and initialize all fields. +** +** (2) Tell SQLite (via the sqlite3_declare_vtab() interface) what the +** result set of queries against completion will look like. +*/ +static int completionConnect( + sqlite3 *db, + void *pAux, + int argc, const char *const*argv, + sqlite3_vtab **ppVtab, + char **pzErr +){ + completion_vtab *pNew; + int rc; + + (void)(pAux); /* Unused parameter */ + (void)(argc); /* Unused parameter */ + (void)(argv); /* Unused parameter */ + (void)(pzErr); /* Unused parameter */ + +/* Column numbers */ +#define COMPLETION_COLUMN_CANDIDATE 0 /* Suggested completion of the input */ +#define COMPLETION_COLUMN_PREFIX 1 /* Prefix of the word to be completed */ +#define COMPLETION_COLUMN_WHOLELINE 2 /* Entire line seen so far */ +#define COMPLETION_COLUMN_PHASE 3 /* ePhase - used for debugging only */ + + sqlite3_vtab_config(db, SQLITE_VTAB_INNOCUOUS); + rc = sqlite3_declare_vtab(db, + "CREATE TABLE x(" + " candidate TEXT," + " prefix TEXT HIDDEN," + " wholeline TEXT HIDDEN," + " phase INT HIDDEN" /* Used for debugging only */ + ")"); + if( rc==SQLITE_OK ){ + pNew = sqlite3_malloc( sizeof(*pNew) ); + *ppVtab = (sqlite3_vtab*)pNew; + if( pNew==0 ) return SQLITE_NOMEM; + memset(pNew, 0, sizeof(*pNew)); + pNew->db = db; + } + return rc; +} + +/* +** This method is the destructor for completion_cursor objects. +*/ +static int completionDisconnect(sqlite3_vtab *pVtab){ + sqlite3_free(pVtab); + return SQLITE_OK; +} + +/* +** Constructor for a new completion_cursor object. +*/ +static int completionOpen(sqlite3_vtab *p, sqlite3_vtab_cursor **ppCursor){ + completion_cursor *pCur; + pCur = sqlite3_malloc( sizeof(*pCur) ); + if( pCur==0 ) return SQLITE_NOMEM; + memset(pCur, 0, sizeof(*pCur)); + pCur->db = ((completion_vtab*)p)->db; + *ppCursor = &pCur->base; + return SQLITE_OK; +} + +/* +** Reset the completion_cursor. +*/ +static void completionCursorReset(completion_cursor *pCur){ + sqlite3_free(pCur->zPrefix); pCur->zPrefix = 0; pCur->nPrefix = 0; + sqlite3_free(pCur->zLine); pCur->zLine = 0; pCur->nLine = 0; + sqlite3_finalize(pCur->pStmt); pCur->pStmt = 0; + pCur->j = 0; +} + +/* +** Destructor for a completion_cursor. +*/ +static int completionClose(sqlite3_vtab_cursor *cur){ + completionCursorReset((completion_cursor*)cur); + sqlite3_free(cur); + return SQLITE_OK; +} + +/* +** Advance a completion_cursor to its next row of output. +** +** The ->ePhase, ->j, and ->pStmt fields of the completion_cursor object +** record the current state of the scan. This routine sets ->zCurrentRow +** to the current row of output and then returns. If no more rows remain, +** then ->ePhase is set to COMPLETION_EOF which will signal the virtual +** table that has reached the end of its scan. +** +** The current implementation just lists potential identifiers and +** keywords and filters them by zPrefix. Future enhancements should +** take zLine into account to try to restrict the set of identifiers and +** keywords based on what would be legal at the current point of input. +*/ +static int completionNext(sqlite3_vtab_cursor *cur){ + completion_cursor *pCur = (completion_cursor*)cur; + int eNextPhase = 0; /* Next phase to try if current phase reaches end */ + int iCol = -1; /* If >=0, step pCur->pStmt and use the i-th column */ + pCur->iRowid++; + while( pCur->ePhase!=COMPLETION_EOF ){ + switch( pCur->ePhase ){ + case COMPLETION_KEYWORDS: { + if( pCur->j >= sqlite3_keyword_count() ){ + pCur->zCurrentRow = 0; + pCur->ePhase = COMPLETION_DATABASES; + }else{ + sqlite3_keyword_name(pCur->j++, &pCur->zCurrentRow, &pCur->szRow); + } + iCol = -1; + break; + } + case COMPLETION_DATABASES: { + if( pCur->pStmt==0 ){ + sqlite3_prepare_v2(pCur->db, "PRAGMA database_list", -1, + &pCur->pStmt, 0); + } + iCol = 1; + eNextPhase = COMPLETION_TABLES; + break; + } + case COMPLETION_TABLES: { + if( pCur->pStmt==0 ){ + sqlite3_stmt *pS2; + char *zSql = 0; + const char *zSep = ""; + sqlite3_prepare_v2(pCur->db, "PRAGMA database_list", -1, &pS2, 0); + while( sqlite3_step(pS2)==SQLITE_ROW ){ + const char *zDb = (const char*)sqlite3_column_text(pS2, 1); + zSql = sqlite3_mprintf( + "%z%s" + "SELECT name FROM \"%w\".sqlite_schema", + zSql, zSep, zDb + ); + if( zSql==0 ) return SQLITE_NOMEM; + zSep = " UNION "; + } + sqlite3_finalize(pS2); + sqlite3_prepare_v2(pCur->db, zSql, -1, &pCur->pStmt, 0); + sqlite3_free(zSql); + } + iCol = 0; + eNextPhase = COMPLETION_COLUMNS; + break; + } + case COMPLETION_COLUMNS: { + if( pCur->pStmt==0 ){ + sqlite3_stmt *pS2; + char *zSql = 0; + const char *zSep = ""; + sqlite3_prepare_v2(pCur->db, "PRAGMA database_list", -1, &pS2, 0); + while( sqlite3_step(pS2)==SQLITE_ROW ){ + const char *zDb = (const char*)sqlite3_column_text(pS2, 1); + zSql = sqlite3_mprintf( + "%z%s" + "SELECT pti.name FROM \"%w\".sqlite_schema AS sm" + " JOIN pragma_table_info(sm.name,%Q) AS pti" + " WHERE sm.type='table'", + zSql, zSep, zDb, zDb + ); + if( zSql==0 ) return SQLITE_NOMEM; + zSep = " UNION "; + } + sqlite3_finalize(pS2); + sqlite3_prepare_v2(pCur->db, zSql, -1, &pCur->pStmt, 0); + sqlite3_free(zSql); + } + iCol = 0; + eNextPhase = COMPLETION_EOF; + break; + } + } + if( iCol<0 ){ + /* This case is when the phase presets zCurrentRow */ + if( pCur->zCurrentRow==0 ) continue; + }else{ + if( sqlite3_step(pCur->pStmt)==SQLITE_ROW ){ + /* Extract the next row of content */ + pCur->zCurrentRow = (const char*)sqlite3_column_text(pCur->pStmt, iCol); + pCur->szRow = sqlite3_column_bytes(pCur->pStmt, iCol); + }else{ + /* When all rows are finished, advance to the next phase */ + sqlite3_finalize(pCur->pStmt); + pCur->pStmt = 0; + pCur->ePhase = eNextPhase; + continue; + } + } + if( pCur->nPrefix==0 ) break; + if( pCur->nPrefix<=pCur->szRow + && sqlite3_strnicmp(pCur->zPrefix, pCur->zCurrentRow, pCur->nPrefix)==0 + ){ + break; + } + } + + return SQLITE_OK; +} + +/* +** Return values of columns for the row at which the completion_cursor +** is currently pointing. +*/ +static int completionColumn( + sqlite3_vtab_cursor *cur, /* The cursor */ + sqlite3_context *ctx, /* First argument to sqlite3_result_...() */ + int i /* Which column to return */ +){ + completion_cursor *pCur = (completion_cursor*)cur; + switch( i ){ + case COMPLETION_COLUMN_CANDIDATE: { + sqlite3_result_text(ctx, pCur->zCurrentRow, pCur->szRow,SQLITE_TRANSIENT); + break; + } + case COMPLETION_COLUMN_PREFIX: { + sqlite3_result_text(ctx, pCur->zPrefix, -1, SQLITE_TRANSIENT); + break; + } + case COMPLETION_COLUMN_WHOLELINE: { + sqlite3_result_text(ctx, pCur->zLine, -1, SQLITE_TRANSIENT); + break; + } + case COMPLETION_COLUMN_PHASE: { + sqlite3_result_int(ctx, pCur->ePhase); + break; + } + } + return SQLITE_OK; +} + +/* +** Return the rowid for the current row. In this implementation, the +** rowid is the same as the output value. +*/ +static int completionRowid(sqlite3_vtab_cursor *cur, sqlite_int64 *pRowid){ + completion_cursor *pCur = (completion_cursor*)cur; + *pRowid = pCur->iRowid; + return SQLITE_OK; +} + +/* +** Return TRUE if the cursor has been moved off of the last +** row of output. +*/ +static int completionEof(sqlite3_vtab_cursor *cur){ + completion_cursor *pCur = (completion_cursor*)cur; + return pCur->ePhase >= COMPLETION_EOF; +} + +/* +** This method is called to "rewind" the completion_cursor object back +** to the first row of output. This method is always called at least +** once prior to any call to completionColumn() or completionRowid() or +** completionEof(). +*/ +static int completionFilter( + sqlite3_vtab_cursor *pVtabCursor, + int idxNum, const char *idxStr, + int argc, sqlite3_value **argv +){ + completion_cursor *pCur = (completion_cursor *)pVtabCursor; + int iArg = 0; + (void)(idxStr); /* Unused parameter */ + (void)(argc); /* Unused parameter */ + completionCursorReset(pCur); + if( idxNum & 1 ){ + pCur->nPrefix = sqlite3_value_bytes(argv[iArg]); + if( pCur->nPrefix>0 ){ + pCur->zPrefix = sqlite3_mprintf("%s", sqlite3_value_text(argv[iArg])); + if( pCur->zPrefix==0 ) return SQLITE_NOMEM; + } + iArg = 1; + } + if( idxNum & 2 ){ + pCur->nLine = sqlite3_value_bytes(argv[iArg]); + if( pCur->nLine>0 ){ + pCur->zLine = sqlite3_mprintf("%s", sqlite3_value_text(argv[iArg])); + if( pCur->zLine==0 ) return SQLITE_NOMEM; + } + } + if( pCur->zLine!=0 && pCur->zPrefix==0 ){ + int i = pCur->nLine; + while( i>0 && (isalnum(pCur->zLine[i-1]) || pCur->zLine[i-1]=='_') ){ + i--; + } + pCur->nPrefix = pCur->nLine - i; + if( pCur->nPrefix>0 ){ + pCur->zPrefix = sqlite3_mprintf("%.*s", pCur->nPrefix, pCur->zLine + i); + if( pCur->zPrefix==0 ) return SQLITE_NOMEM; + } + } + pCur->iRowid = 0; + pCur->ePhase = COMPLETION_FIRST_PHASE; + return completionNext(pVtabCursor); +} + +/* +** SQLite will invoke this method one or more times while planning a query +** that uses the completion virtual table. This routine needs to create +** a query plan for each invocation and compute an estimated cost for that +** plan. +** +** There are two hidden parameters that act as arguments to the table-valued +** function: "prefix" and "wholeline". Bit 0 of idxNum is set if "prefix" +** is available and bit 1 is set if "wholeline" is available. +*/ +static int completionBestIndex( + sqlite3_vtab *tab, + sqlite3_index_info *pIdxInfo +){ + int i; /* Loop over constraints */ + int idxNum = 0; /* The query plan bitmask */ + int prefixIdx = -1; /* Index of the start= constraint, or -1 if none */ + int wholelineIdx = -1; /* Index of the stop= constraint, or -1 if none */ + int nArg = 0; /* Number of arguments that completeFilter() expects */ + const struct sqlite3_index_constraint *pConstraint; + + (void)(tab); /* Unused parameter */ + pConstraint = pIdxInfo->aConstraint; + for(i=0; inConstraint; i++, pConstraint++){ + if( pConstraint->usable==0 ) continue; + if( pConstraint->op!=SQLITE_INDEX_CONSTRAINT_EQ ) continue; + switch( pConstraint->iColumn ){ + case COMPLETION_COLUMN_PREFIX: + prefixIdx = i; + idxNum |= 1; + break; + case COMPLETION_COLUMN_WHOLELINE: + wholelineIdx = i; + idxNum |= 2; + break; + } + } + if( prefixIdx>=0 ){ + pIdxInfo->aConstraintUsage[prefixIdx].argvIndex = ++nArg; + pIdxInfo->aConstraintUsage[prefixIdx].omit = 1; + } + if( wholelineIdx>=0 ){ + pIdxInfo->aConstraintUsage[wholelineIdx].argvIndex = ++nArg; + pIdxInfo->aConstraintUsage[wholelineIdx].omit = 1; + } + pIdxInfo->idxNum = idxNum; + pIdxInfo->estimatedCost = (double)5000 - 1000*nArg; + pIdxInfo->estimatedRows = 500 - 100*nArg; + return SQLITE_OK; +} + +/* +** This following structure defines all the methods for the +** completion virtual table. +*/ +static sqlite3_module completionModule = { + 0, /* iVersion */ + 0, /* xCreate */ + completionConnect, /* xConnect */ + completionBestIndex, /* xBestIndex */ + completionDisconnect, /* xDisconnect */ + 0, /* xDestroy */ + completionOpen, /* xOpen - open a cursor */ + completionClose, /* xClose - close a cursor */ + completionFilter, /* xFilter - configure scan constraints */ + completionNext, /* xNext - advance a cursor */ + completionEof, /* xEof - check for end of scan */ + completionColumn, /* xColumn - read data */ + completionRowid, /* xRowid - read data */ + 0, /* xUpdate */ + 0, /* xBegin */ + 0, /* xSync */ + 0, /* xCommit */ + 0, /* xRollback */ + 0, /* xFindMethod */ + 0, /* xRename */ + 0, /* xSavepoint */ + 0, /* xRelease */ + 0, /* xRollbackTo */ + 0 /* xShadowName */ +}; + +#endif /* SQLITE_OMIT_VIRTUALTABLE */ + +int sqlite3CompletionVtabInit(sqlite3 *db){ + int rc = SQLITE_OK; +#ifndef SQLITE_OMIT_VIRTUALTABLE + rc = sqlite3_create_module(db, "completion", &completionModule, 0); +#endif + return rc; +} + +#ifdef _WIN32 + +#endif +int sqlite3_completion_init( + sqlite3 *db, + char **pzErrMsg, + const sqlite3_api_routines *pApi +){ + int rc = SQLITE_OK; + SQLITE_EXTENSION_INIT2(pApi); + (void)(pzErrMsg); /* Unused parameter */ +#ifndef SQLITE_OMIT_VIRTUALTABLE + rc = sqlite3CompletionVtabInit(db); +#endif + return rc; +} + +/************************* End ../ext/misc/completion.c ********************/ +/************************* Begin ../ext/misc/appendvfs.c ******************/ +/* +** 2017-10-20 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +****************************************************************************** +** +** This file implements a VFS shim that allows an SQLite database to be +** appended onto the end of some other file, such as an executable. +** +** A special record must appear at the end of the file that identifies the +** file as an appended database and provides the offset to the first page +** of the exposed content. (Or, it is the length of the content prefix.) +** For best performance page 1 should be located at a disk page boundary, +** though that is not required. +** +** When opening a database using this VFS, the connection might treat +** the file as an ordinary SQLite database, or it might treat it as a +** database appended onto some other file. The decision is made by +** applying the following rules in order: +** +** (1) An empty file is an ordinary database. +** +** (2) If the file ends with the appendvfs trailer string +** "Start-Of-SQLite3-NNNNNNNN" that file is an appended database. +** +** (3) If the file begins with the standard SQLite prefix string +** "SQLite format 3", that file is an ordinary database. +** +** (4) If none of the above apply and the SQLITE_OPEN_CREATE flag is +** set, then a new database is appended to the already existing file. +** +** (5) Otherwise, SQLITE_CANTOPEN is returned. +** +** To avoid unnecessary complications with the PENDING_BYTE, the size of +** the file containing the database is limited to 1GiB. (1073741824 bytes) +** This VFS will not read or write past the 1GiB mark. This restriction +** might be lifted in future versions. For now, if you need a larger +** database, then keep it in a separate file. +** +** If the file being opened is a plain database (not an appended one), then +** this shim is a pass-through into the default underlying VFS. (rule 3) +**/ +/* #include "sqlite3ext.h" */ +SQLITE_EXTENSION_INIT1 +#include +#include + +/* The append mark at the end of the database is: +** +** Start-Of-SQLite3-NNNNNNNN +** 123456789 123456789 12345 +** +** The NNNNNNNN represents a 64-bit big-endian unsigned integer which is +** the offset to page 1, and also the length of the prefix content. +*/ +#define APND_MARK_PREFIX "Start-Of-SQLite3-" +#define APND_MARK_PREFIX_SZ 17 +#define APND_MARK_FOS_SZ 8 +#define APND_MARK_SIZE (APND_MARK_PREFIX_SZ+APND_MARK_FOS_SZ) + +/* +** Maximum size of the combined prefix + database + append-mark. This +** must be less than 0x40000000 to avoid locking issues on Windows. +*/ +#define APND_MAX_SIZE (0x40000000) + +/* +** Try to align the database to an even multiple of APND_ROUNDUP bytes. +*/ +#ifndef APND_ROUNDUP +#define APND_ROUNDUP 4096 +#endif +#define APND_ALIGN_MASK ((sqlite3_int64)(APND_ROUNDUP-1)) +#define APND_START_ROUNDUP(fsz) (((fsz)+APND_ALIGN_MASK) & ~APND_ALIGN_MASK) + +/* +** Forward declaration of objects used by this utility +*/ +typedef struct sqlite3_vfs ApndVfs; +typedef struct ApndFile ApndFile; + +/* Access to a lower-level VFS that (might) implement dynamic loading, +** access to randomness, etc. +*/ +#define ORIGVFS(p) ((sqlite3_vfs*)((p)->pAppData)) +#define ORIGFILE(p) ((sqlite3_file*)(((ApndFile*)(p))+1)) + +/* An open appendvfs file +** +** An instance of this structure describes the appended database file. +** A separate sqlite3_file object is always appended. The appended +** sqlite3_file object (which can be accessed using ORIGFILE()) describes +** the entire file, including the prefix, the database, and the +** append-mark. +** +** The structure of an AppendVFS database is like this: +** +** +-------------+---------+----------+-------------+ +** | prefix-file | padding | database | append-mark | +** +-------------+---------+----------+-------------+ +** ^ ^ +** | | +** iPgOne iMark +** +** +** "prefix file" - file onto which the database has been appended. +** "padding" - zero or more bytes inserted so that "database" +** starts on an APND_ROUNDUP boundary +** "database" - The SQLite database file +** "append-mark" - The 25-byte "Start-Of-SQLite3-NNNNNNNN" that indicates +** the offset from the start of prefix-file to the start +** of "database". +** +** The size of the database is iMark - iPgOne. +** +** The NNNNNNNN in the "Start-Of-SQLite3-NNNNNNNN" suffix is the value +** of iPgOne stored as a big-ending 64-bit integer. +** +** iMark will be the size of the underlying file minus 25 (APND_MARKSIZE). +** Or, iMark is -1 to indicate that it has not yet been written. +*/ +struct ApndFile { + sqlite3_file base; /* Subclass. MUST BE FIRST! */ + sqlite3_int64 iPgOne; /* Offset to the start of the database */ + sqlite3_int64 iMark; /* Offset of the append mark. -1 if unwritten */ + /* Always followed by another sqlite3_file that describes the whole file */ +}; + +/* +** Methods for ApndFile +*/ +static int apndClose(sqlite3_file*); +static int apndRead(sqlite3_file*, void*, int iAmt, sqlite3_int64 iOfst); +static int apndWrite(sqlite3_file*,const void*,int iAmt, sqlite3_int64 iOfst); +static int apndTruncate(sqlite3_file*, sqlite3_int64 size); +static int apndSync(sqlite3_file*, int flags); +static int apndFileSize(sqlite3_file*, sqlite3_int64 *pSize); +static int apndLock(sqlite3_file*, int); +static int apndUnlock(sqlite3_file*, int); +static int apndCheckReservedLock(sqlite3_file*, int *pResOut); +static int apndFileControl(sqlite3_file*, int op, void *pArg); +static int apndSectorSize(sqlite3_file*); +static int apndDeviceCharacteristics(sqlite3_file*); +static int apndShmMap(sqlite3_file*, int iPg, int pgsz, int, void volatile**); +static int apndShmLock(sqlite3_file*, int offset, int n, int flags); +static void apndShmBarrier(sqlite3_file*); +static int apndShmUnmap(sqlite3_file*, int deleteFlag); +static int apndFetch(sqlite3_file*, sqlite3_int64 iOfst, int iAmt, void **pp); +static int apndUnfetch(sqlite3_file*, sqlite3_int64 iOfst, void *p); + +/* +** Methods for ApndVfs +*/ +static int apndOpen(sqlite3_vfs*, const char *, sqlite3_file*, int , int *); +static int apndDelete(sqlite3_vfs*, const char *zName, int syncDir); +static int apndAccess(sqlite3_vfs*, const char *zName, int flags, int *); +static int apndFullPathname(sqlite3_vfs*, const char *zName, int, char *zOut); +static void *apndDlOpen(sqlite3_vfs*, const char *zFilename); +static void apndDlError(sqlite3_vfs*, int nByte, char *zErrMsg); +static void (*apndDlSym(sqlite3_vfs *pVfs, void *p, const char*zSym))(void); +static void apndDlClose(sqlite3_vfs*, void*); +static int apndRandomness(sqlite3_vfs*, int nByte, char *zOut); +static int apndSleep(sqlite3_vfs*, int microseconds); +static int apndCurrentTime(sqlite3_vfs*, double*); +static int apndGetLastError(sqlite3_vfs*, int, char *); +static int apndCurrentTimeInt64(sqlite3_vfs*, sqlite3_int64*); +static int apndSetSystemCall(sqlite3_vfs*, const char*,sqlite3_syscall_ptr); +static sqlite3_syscall_ptr apndGetSystemCall(sqlite3_vfs*, const char *z); +static const char *apndNextSystemCall(sqlite3_vfs*, const char *zName); + +static sqlite3_vfs apnd_vfs = { + 3, /* iVersion (set when registered) */ + 0, /* szOsFile (set when registered) */ + 1024, /* mxPathname */ + 0, /* pNext */ + "apndvfs", /* zName */ + 0, /* pAppData (set when registered) */ + apndOpen, /* xOpen */ + apndDelete, /* xDelete */ + apndAccess, /* xAccess */ + apndFullPathname, /* xFullPathname */ + apndDlOpen, /* xDlOpen */ + apndDlError, /* xDlError */ + apndDlSym, /* xDlSym */ + apndDlClose, /* xDlClose */ + apndRandomness, /* xRandomness */ + apndSleep, /* xSleep */ + apndCurrentTime, /* xCurrentTime */ + apndGetLastError, /* xGetLastError */ + apndCurrentTimeInt64, /* xCurrentTimeInt64 */ + apndSetSystemCall, /* xSetSystemCall */ + apndGetSystemCall, /* xGetSystemCall */ + apndNextSystemCall /* xNextSystemCall */ +}; + +static const sqlite3_io_methods apnd_io_methods = { + 3, /* iVersion */ + apndClose, /* xClose */ + apndRead, /* xRead */ + apndWrite, /* xWrite */ + apndTruncate, /* xTruncate */ + apndSync, /* xSync */ + apndFileSize, /* xFileSize */ + apndLock, /* xLock */ + apndUnlock, /* xUnlock */ + apndCheckReservedLock, /* xCheckReservedLock */ + apndFileControl, /* xFileControl */ + apndSectorSize, /* xSectorSize */ + apndDeviceCharacteristics, /* xDeviceCharacteristics */ + apndShmMap, /* xShmMap */ + apndShmLock, /* xShmLock */ + apndShmBarrier, /* xShmBarrier */ + apndShmUnmap, /* xShmUnmap */ + apndFetch, /* xFetch */ + apndUnfetch /* xUnfetch */ +}; + +/* +** Close an apnd-file. +*/ +static int apndClose(sqlite3_file *pFile){ + pFile = ORIGFILE(pFile); + return pFile->pMethods->xClose(pFile); +} + +/* +** Read data from an apnd-file. +*/ +static int apndRead( + sqlite3_file *pFile, + void *zBuf, + int iAmt, + sqlite_int64 iOfst +){ + ApndFile *paf = (ApndFile *)pFile; + pFile = ORIGFILE(pFile); + return pFile->pMethods->xRead(pFile, zBuf, iAmt, paf->iPgOne+iOfst); +} + +/* +** Add the append-mark onto what should become the end of the file. +* If and only if this succeeds, internal ApndFile.iMark is updated. +* Parameter iWriteEnd is the appendvfs-relative offset of the new mark. +*/ +static int apndWriteMark( + ApndFile *paf, + sqlite3_file *pFile, + sqlite_int64 iWriteEnd +){ + sqlite_int64 iPgOne = paf->iPgOne; + unsigned char a[APND_MARK_SIZE]; + int i = APND_MARK_FOS_SZ; + int rc; + assert(pFile == ORIGFILE(paf)); + memcpy(a, APND_MARK_PREFIX, APND_MARK_PREFIX_SZ); + while( --i >= 0 ){ + a[APND_MARK_PREFIX_SZ+i] = (unsigned char)(iPgOne & 0xff); + iPgOne >>= 8; + } + iWriteEnd += paf->iPgOne; + if( SQLITE_OK==(rc = pFile->pMethods->xWrite + (pFile, a, APND_MARK_SIZE, iWriteEnd)) ){ + paf->iMark = iWriteEnd; + } + return rc; +} + +/* +** Write data to an apnd-file. +*/ +static int apndWrite( + sqlite3_file *pFile, + const void *zBuf, + int iAmt, + sqlite_int64 iOfst +){ + ApndFile *paf = (ApndFile *)pFile; + sqlite_int64 iWriteEnd = iOfst + iAmt; + if( iWriteEnd>=APND_MAX_SIZE ) return SQLITE_FULL; + pFile = ORIGFILE(pFile); + /* If append-mark is absent or will be overwritten, write it. */ + if( paf->iMark < 0 || paf->iPgOne + iWriteEnd > paf->iMark ){ + int rc = apndWriteMark(paf, pFile, iWriteEnd); + if( SQLITE_OK!=rc ) return rc; + } + return pFile->pMethods->xWrite(pFile, zBuf, iAmt, paf->iPgOne+iOfst); +} + +/* +** Truncate an apnd-file. +*/ +static int apndTruncate(sqlite3_file *pFile, sqlite_int64 size){ + ApndFile *paf = (ApndFile *)pFile; + pFile = ORIGFILE(pFile); + /* The append mark goes out first so truncate failure does not lose it. */ + if( SQLITE_OK!=apndWriteMark(paf, pFile, size) ) return SQLITE_IOERR; + /* Truncate underlying file just past append mark */ + return pFile->pMethods->xTruncate(pFile, paf->iMark+APND_MARK_SIZE); +} + +/* +** Sync an apnd-file. +*/ +static int apndSync(sqlite3_file *pFile, int flags){ + pFile = ORIGFILE(pFile); + return pFile->pMethods->xSync(pFile, flags); +} + +/* +** Return the current file-size of an apnd-file. +** If the append mark is not yet there, the file-size is 0. +*/ +static int apndFileSize(sqlite3_file *pFile, sqlite_int64 *pSize){ + ApndFile *paf = (ApndFile *)pFile; + *pSize = ( paf->iMark >= 0 )? (paf->iMark - paf->iPgOne) : 0; + return SQLITE_OK; +} + +/* +** Lock an apnd-file. +*/ +static int apndLock(sqlite3_file *pFile, int eLock){ + pFile = ORIGFILE(pFile); + return pFile->pMethods->xLock(pFile, eLock); +} + +/* +** Unlock an apnd-file. +*/ +static int apndUnlock(sqlite3_file *pFile, int eLock){ + pFile = ORIGFILE(pFile); + return pFile->pMethods->xUnlock(pFile, eLock); +} + +/* +** Check if another file-handle holds a RESERVED lock on an apnd-file. +*/ +static int apndCheckReservedLock(sqlite3_file *pFile, int *pResOut){ + pFile = ORIGFILE(pFile); + return pFile->pMethods->xCheckReservedLock(pFile, pResOut); +} + +/* +** File control method. For custom operations on an apnd-file. +*/ +static int apndFileControl(sqlite3_file *pFile, int op, void *pArg){ + ApndFile *paf = (ApndFile *)pFile; + int rc; + pFile = ORIGFILE(pFile); + if( op==SQLITE_FCNTL_SIZE_HINT ) *(sqlite3_int64*)pArg += paf->iPgOne; + rc = pFile->pMethods->xFileControl(pFile, op, pArg); + if( rc==SQLITE_OK && op==SQLITE_FCNTL_VFSNAME ){ + *(char**)pArg = sqlite3_mprintf("apnd(%lld)/%z", paf->iPgOne,*(char**)pArg); + } + return rc; +} + +/* +** Return the sector-size in bytes for an apnd-file. +*/ +static int apndSectorSize(sqlite3_file *pFile){ + pFile = ORIGFILE(pFile); + return pFile->pMethods->xSectorSize(pFile); +} + +/* +** Return the device characteristic flags supported by an apnd-file. +*/ +static int apndDeviceCharacteristics(sqlite3_file *pFile){ + pFile = ORIGFILE(pFile); + return pFile->pMethods->xDeviceCharacteristics(pFile); +} + +/* Create a shared memory file mapping */ +static int apndShmMap( + sqlite3_file *pFile, + int iPg, + int pgsz, + int bExtend, + void volatile **pp +){ + pFile = ORIGFILE(pFile); + return pFile->pMethods->xShmMap(pFile,iPg,pgsz,bExtend,pp); +} + +/* Perform locking on a shared-memory segment */ +static int apndShmLock(sqlite3_file *pFile, int offset, int n, int flags){ + pFile = ORIGFILE(pFile); + return pFile->pMethods->xShmLock(pFile,offset,n,flags); +} + +/* Memory barrier operation on shared memory */ +static void apndShmBarrier(sqlite3_file *pFile){ + pFile = ORIGFILE(pFile); + pFile->pMethods->xShmBarrier(pFile); +} + +/* Unmap a shared memory segment */ +static int apndShmUnmap(sqlite3_file *pFile, int deleteFlag){ + pFile = ORIGFILE(pFile); + return pFile->pMethods->xShmUnmap(pFile,deleteFlag); +} + +/* Fetch a page of a memory-mapped file */ +static int apndFetch( + sqlite3_file *pFile, + sqlite3_int64 iOfst, + int iAmt, + void **pp +){ + ApndFile *p = (ApndFile *)pFile; + if( p->iMark < 0 || iOfst+iAmt > p->iMark ){ + return SQLITE_IOERR; /* Cannot read what is not yet there. */ + } + pFile = ORIGFILE(pFile); + return pFile->pMethods->xFetch(pFile, iOfst+p->iPgOne, iAmt, pp); +} + +/* Release a memory-mapped page */ +static int apndUnfetch(sqlite3_file *pFile, sqlite3_int64 iOfst, void *pPage){ + ApndFile *p = (ApndFile *)pFile; + pFile = ORIGFILE(pFile); + return pFile->pMethods->xUnfetch(pFile, iOfst+p->iPgOne, pPage); +} + +/* +** Try to read the append-mark off the end of a file. Return the +** start of the appended database if the append-mark is present. +** If there is no valid append-mark, return -1; +** +** An append-mark is only valid if the NNNNNNNN start-of-database offset +** indicates that the appended database contains at least one page. The +** start-of-database value must be a multiple of 512. +*/ +static sqlite3_int64 apndReadMark(sqlite3_int64 sz, sqlite3_file *pFile){ + int rc, i; + sqlite3_int64 iMark; + int msbs = 8 * (APND_MARK_FOS_SZ-1); + unsigned char a[APND_MARK_SIZE]; + + if( APND_MARK_SIZE!=(sz & 0x1ff) ) return -1; + rc = pFile->pMethods->xRead(pFile, a, APND_MARK_SIZE, sz-APND_MARK_SIZE); + if( rc ) return -1; + if( memcmp(a, APND_MARK_PREFIX, APND_MARK_PREFIX_SZ)!=0 ) return -1; + iMark = ((sqlite3_int64)(a[APND_MARK_PREFIX_SZ] & 0x7f)) << msbs; + for(i=1; i<8; i++){ + msbs -= 8; + iMark |= (sqlite3_int64)a[APND_MARK_PREFIX_SZ+i]< (sz - APND_MARK_SIZE - 512) ) return -1; + if( iMark & 0x1ff ) return -1; + return iMark; +} + +static const char apvfsSqliteHdr[] = "SQLite format 3"; +/* +** Check to see if the file is an appendvfs SQLite database file. +** Return true iff it is such. Parameter sz is the file's size. +*/ +static int apndIsAppendvfsDatabase(sqlite3_int64 sz, sqlite3_file *pFile){ + int rc; + char zHdr[16]; + sqlite3_int64 iMark = apndReadMark(sz, pFile); + if( iMark>=0 ){ + /* If file has the correct end-marker, the expected odd size, and the + ** SQLite DB type marker where the end-marker puts it, then it + ** is an appendvfs database. + */ + rc = pFile->pMethods->xRead(pFile, zHdr, sizeof(zHdr), iMark); + if( SQLITE_OK==rc + && memcmp(zHdr, apvfsSqliteHdr, sizeof(zHdr))==0 + && (sz & 0x1ff) == APND_MARK_SIZE + && sz>=512+APND_MARK_SIZE + ){ + return 1; /* It's an appendvfs database */ + } + } + return 0; +} + +/* +** Check to see if the file is an ordinary SQLite database file. +** Return true iff so. Parameter sz is the file's size. +*/ +static int apndIsOrdinaryDatabaseFile(sqlite3_int64 sz, sqlite3_file *pFile){ + char zHdr[16]; + if( apndIsAppendvfsDatabase(sz, pFile) /* rule 2 */ + || (sz & 0x1ff) != 0 + || SQLITE_OK!=pFile->pMethods->xRead(pFile, zHdr, sizeof(zHdr), 0) + || memcmp(zHdr, apvfsSqliteHdr, sizeof(zHdr))!=0 + ){ + return 0; + }else{ + return 1; + } +} + +/* +** Open an apnd file handle. +*/ +static int apndOpen( + sqlite3_vfs *pApndVfs, + const char *zName, + sqlite3_file *pFile, + int flags, + int *pOutFlags +){ + ApndFile *pApndFile = (ApndFile*)pFile; + sqlite3_file *pBaseFile = ORIGFILE(pFile); + sqlite3_vfs *pBaseVfs = ORIGVFS(pApndVfs); + int rc; + sqlite3_int64 sz = 0; + if( (flags & SQLITE_OPEN_MAIN_DB)==0 ){ + /* The appendvfs is not to be used for transient or temporary databases. + ** Just use the base VFS open to initialize the given file object and + ** open the underlying file. (Appendvfs is then unused for this file.) + */ + return pBaseVfs->xOpen(pBaseVfs, zName, pFile, flags, pOutFlags); + } + memset(pApndFile, 0, sizeof(ApndFile)); + pFile->pMethods = &apnd_io_methods; + pApndFile->iMark = -1; /* Append mark not yet written */ + + rc = pBaseVfs->xOpen(pBaseVfs, zName, pBaseFile, flags, pOutFlags); + if( rc==SQLITE_OK ){ + rc = pBaseFile->pMethods->xFileSize(pBaseFile, &sz); + if( rc ){ + pBaseFile->pMethods->xClose(pBaseFile); + } + } + if( rc ){ + pFile->pMethods = 0; + return rc; + } + if( apndIsOrdinaryDatabaseFile(sz, pBaseFile) ){ + /* The file being opened appears to be just an ordinary DB. Copy + ** the base dispatch-table so this instance mimics the base VFS. + */ + memmove(pApndFile, pBaseFile, pBaseVfs->szOsFile); + return SQLITE_OK; + } + pApndFile->iPgOne = apndReadMark(sz, pFile); + if( pApndFile->iPgOne>=0 ){ + pApndFile->iMark = sz - APND_MARK_SIZE; /* Append mark found */ + return SQLITE_OK; + } + if( (flags & SQLITE_OPEN_CREATE)==0 ){ + pBaseFile->pMethods->xClose(pBaseFile); + rc = SQLITE_CANTOPEN; + pFile->pMethods = 0; + }else{ + /* Round newly added appendvfs location to #define'd page boundary. + ** Note that nothing has yet been written to the underlying file. + ** The append mark will be written along with first content write. + ** Until then, paf->iMark value indicates it is not yet written. + */ + pApndFile->iPgOne = APND_START_ROUNDUP(sz); + } + return rc; +} + +/* +** Delete an apnd file. +** For an appendvfs, this could mean delete the appendvfs portion, +** leaving the appendee as it was before it gained an appendvfs. +** For now, this code deletes the underlying file too. +*/ +static int apndDelete(sqlite3_vfs *pVfs, const char *zPath, int dirSync){ + return ORIGVFS(pVfs)->xDelete(ORIGVFS(pVfs), zPath, dirSync); +} + +/* +** All other VFS methods are pass-thrus. +*/ +static int apndAccess( + sqlite3_vfs *pVfs, + const char *zPath, + int flags, + int *pResOut +){ + return ORIGVFS(pVfs)->xAccess(ORIGVFS(pVfs), zPath, flags, pResOut); +} +static int apndFullPathname( + sqlite3_vfs *pVfs, + const char *zPath, + int nOut, + char *zOut +){ + return ORIGVFS(pVfs)->xFullPathname(ORIGVFS(pVfs),zPath,nOut,zOut); +} +static void *apndDlOpen(sqlite3_vfs *pVfs, const char *zPath){ + return ORIGVFS(pVfs)->xDlOpen(ORIGVFS(pVfs), zPath); +} +static void apndDlError(sqlite3_vfs *pVfs, int nByte, char *zErrMsg){ + ORIGVFS(pVfs)->xDlError(ORIGVFS(pVfs), nByte, zErrMsg); +} +static void (*apndDlSym(sqlite3_vfs *pVfs, void *p, const char *zSym))(void){ + return ORIGVFS(pVfs)->xDlSym(ORIGVFS(pVfs), p, zSym); +} +static void apndDlClose(sqlite3_vfs *pVfs, void *pHandle){ + ORIGVFS(pVfs)->xDlClose(ORIGVFS(pVfs), pHandle); +} +static int apndRandomness(sqlite3_vfs *pVfs, int nByte, char *zBufOut){ + return ORIGVFS(pVfs)->xRandomness(ORIGVFS(pVfs), nByte, zBufOut); +} +static int apndSleep(sqlite3_vfs *pVfs, int nMicro){ + return ORIGVFS(pVfs)->xSleep(ORIGVFS(pVfs), nMicro); +} +static int apndCurrentTime(sqlite3_vfs *pVfs, double *pTimeOut){ + return ORIGVFS(pVfs)->xCurrentTime(ORIGVFS(pVfs), pTimeOut); +} +static int apndGetLastError(sqlite3_vfs *pVfs, int a, char *b){ + return ORIGVFS(pVfs)->xGetLastError(ORIGVFS(pVfs), a, b); +} +static int apndCurrentTimeInt64(sqlite3_vfs *pVfs, sqlite3_int64 *p){ + return ORIGVFS(pVfs)->xCurrentTimeInt64(ORIGVFS(pVfs), p); +} +static int apndSetSystemCall( + sqlite3_vfs *pVfs, + const char *zName, + sqlite3_syscall_ptr pCall +){ + return ORIGVFS(pVfs)->xSetSystemCall(ORIGVFS(pVfs),zName,pCall); +} +static sqlite3_syscall_ptr apndGetSystemCall( + sqlite3_vfs *pVfs, + const char *zName +){ + return ORIGVFS(pVfs)->xGetSystemCall(ORIGVFS(pVfs),zName); +} +static const char *apndNextSystemCall(sqlite3_vfs *pVfs, const char *zName){ + return ORIGVFS(pVfs)->xNextSystemCall(ORIGVFS(pVfs), zName); +} + + +#ifdef _WIN32 + +#endif +/* +** This routine is called when the extension is loaded. +** Register the new VFS. +*/ +int sqlite3_appendvfs_init( + sqlite3 *db, + char **pzErrMsg, + const sqlite3_api_routines *pApi +){ + int rc = SQLITE_OK; + sqlite3_vfs *pOrig; + SQLITE_EXTENSION_INIT2(pApi); + (void)pzErrMsg; + (void)db; + pOrig = sqlite3_vfs_find(0); + if( pOrig==0 ) return SQLITE_ERROR; + apnd_vfs.iVersion = pOrig->iVersion; + apnd_vfs.pAppData = pOrig; + apnd_vfs.szOsFile = pOrig->szOsFile + sizeof(ApndFile); + rc = sqlite3_vfs_register(&apnd_vfs, 0); +#ifdef APPENDVFS_TEST + if( rc==SQLITE_OK ){ + rc = sqlite3_auto_extension((void(*)(void))apndvfsRegister); + } +#endif + if( rc==SQLITE_OK ) rc = SQLITE_OK_LOAD_PERMANENTLY; + return rc; +} + +/************************* End ../ext/misc/appendvfs.c ********************/ +/************************* Begin ../ext/misc/memtrace.c ******************/ +/* +** 2019-01-21 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +************************************************************************* +** +** This file implements an extension that uses the SQLITE_CONFIG_MALLOC +** mechanism to add a tracing layer on top of SQLite. If this extension +** is registered prior to sqlite3_initialize(), it will cause all memory +** allocation activities to be logged on standard output, or to some other +** FILE specified by the initializer. +** +** This file needs to be compiled into the application that uses it. +** +** This extension is used to implement the --memtrace option of the +** command-line shell. +*/ +#include +#include +#include + +/* The original memory allocation routines */ +static sqlite3_mem_methods memtraceBase; +static FILE *memtraceOut; + +/* Methods that trace memory allocations */ +static void *memtraceMalloc(int n){ + if( memtraceOut ){ + fprintf(memtraceOut, "MEMTRACE: allocate %d bytes\n", + memtraceBase.xRoundup(n)); + } + return memtraceBase.xMalloc(n); +} +static void memtraceFree(void *p){ + if( p==0 ) return; + if( memtraceOut ){ + fprintf(memtraceOut, "MEMTRACE: free %d bytes\n", memtraceBase.xSize(p)); + } + memtraceBase.xFree(p); +} +static void *memtraceRealloc(void *p, int n){ + if( p==0 ) return memtraceMalloc(n); + if( n==0 ){ + memtraceFree(p); + return 0; + } + if( memtraceOut ){ + fprintf(memtraceOut, "MEMTRACE: resize %d -> %d bytes\n", + memtraceBase.xSize(p), memtraceBase.xRoundup(n)); + } + return memtraceBase.xRealloc(p, n); +} +static int memtraceSize(void *p){ + return memtraceBase.xSize(p); +} +static int memtraceRoundup(int n){ + return memtraceBase.xRoundup(n); +} +static int memtraceInit(void *p){ + return memtraceBase.xInit(p); +} +static void memtraceShutdown(void *p){ + memtraceBase.xShutdown(p); +} + +/* The substitute memory allocator */ +static sqlite3_mem_methods ersaztMethods = { + memtraceMalloc, + memtraceFree, + memtraceRealloc, + memtraceSize, + memtraceRoundup, + memtraceInit, + memtraceShutdown, + 0 +}; + +/* Begin tracing memory allocations to out. */ +int sqlite3MemTraceActivate(FILE *out){ + int rc = SQLITE_OK; + if( memtraceBase.xMalloc==0 ){ + rc = sqlite3_config(SQLITE_CONFIG_GETMALLOC, &memtraceBase); + if( rc==SQLITE_OK ){ + rc = sqlite3_config(SQLITE_CONFIG_MALLOC, &ersaztMethods); + } + } + memtraceOut = out; + return rc; +} + +/* Deactivate memory tracing */ +int sqlite3MemTraceDeactivate(void){ + int rc = SQLITE_OK; + if( memtraceBase.xMalloc!=0 ){ + rc = sqlite3_config(SQLITE_CONFIG_MALLOC, &memtraceBase); + if( rc==SQLITE_OK ){ + memset(&memtraceBase, 0, sizeof(memtraceBase)); + } + } + memtraceOut = 0; + return rc; +} + +/************************* End ../ext/misc/memtrace.c ********************/ +/************************* Begin ../ext/misc/uint.c ******************/ +/* +** 2020-04-14 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +****************************************************************************** +** +** This SQLite extension implements the UINT collating sequence. +** +** UINT works like BINARY for text, except that embedded strings +** of digits compare in numeric order. +** +** * Leading zeros are handled properly, in the sense that +** they do not mess of the maginitude comparison of embedded +** strings of digits. "x00123y" is equal to "x123y". +** +** * Only unsigned integers are recognized. Plus and minus +** signs are ignored. Decimal points and exponential notation +** are ignored. +** +** * Embedded integers can be of arbitrary length. Comparison +** is *not* limited integers that can be expressed as a +** 64-bit machine integer. +*/ +/* #include "sqlite3ext.h" */ +SQLITE_EXTENSION_INIT1 +#include +#include +#include + +/* +** Compare text in lexicographic order, except strings of digits +** compare in numeric order. +*/ +static int uintCollFunc( + void *notUsed, + int nKey1, const void *pKey1, + int nKey2, const void *pKey2 +){ + const unsigned char *zA = (const unsigned char*)pKey1; + const unsigned char *zB = (const unsigned char*)pKey2; + int i=0, j=0, x; + (void)notUsed; + while( i +#include +#include +#include + +/* Mark a function parameter as unused, to suppress nuisance compiler +** warnings. */ +#ifndef UNUSED_PARAMETER +# define UNUSED_PARAMETER(X) (void)(X) +#endif + + +/* A decimal object */ +typedef struct Decimal Decimal; +struct Decimal { + char sign; /* 0 for positive, 1 for negative */ + char oom; /* True if an OOM is encountered */ + char isNull; /* True if holds a NULL rather than a number */ + char isInit; /* True upon initialization */ + int nDigit; /* Total number of digits */ + int nFrac; /* Number of digits to the right of the decimal point */ + signed char *a; /* Array of digits. Most significant first. */ +}; + +/* +** Release memory held by a Decimal, but do not free the object itself. +*/ +static void decimal_clear(Decimal *p){ + sqlite3_free(p->a); +} + +/* +** Destroy a Decimal object +*/ +static void decimal_free(Decimal *p){ + if( p ){ + decimal_clear(p); + sqlite3_free(p); + } +} + +/* +** Allocate a new Decimal object. Initialize it to the number given +** by the input string. +*/ +static Decimal *decimal_new( + sqlite3_context *pCtx, + sqlite3_value *pIn, + int nAlt, + const unsigned char *zAlt +){ + Decimal *p; + int n, i; + const unsigned char *zIn; + int iExp = 0; + p = sqlite3_malloc( sizeof(*p) ); + if( p==0 ) goto new_no_mem; + p->sign = 0; + p->oom = 0; + p->isInit = 1; + p->isNull = 0; + p->nDigit = 0; + p->nFrac = 0; + if( zAlt ){ + n = nAlt, + zIn = zAlt; + }else{ + if( sqlite3_value_type(pIn)==SQLITE_NULL ){ + p->a = 0; + p->isNull = 1; + return p; + } + n = sqlite3_value_bytes(pIn); + zIn = sqlite3_value_text(pIn); + } + p->a = sqlite3_malloc64( n+1 ); + if( p->a==0 ) goto new_no_mem; + for(i=0; isspace(zIn[i]); i++){} + if( zIn[i]=='-' ){ + p->sign = 1; + i++; + }else if( zIn[i]=='+' ){ + i++; + } + while( i='0' && c<='9' ){ + p->a[p->nDigit++] = c - '0'; + }else if( c=='.' ){ + p->nFrac = p->nDigit + 1; + }else if( c=='e' || c=='E' ){ + int j = i+1; + int neg = 0; + if( j>=n ) break; + if( zIn[j]=='-' ){ + neg = 1; + j++; + }else if( zIn[j]=='+' ){ + j++; + } + while( j='0' && zIn[j]<='9' ){ + iExp = iExp*10 + zIn[j] - '0'; + } + j++; + } + if( neg ) iExp = -iExp; + break; + } + i++; + } + if( p->nFrac ){ + p->nFrac = p->nDigit - (p->nFrac - 1); + } + if( iExp>0 ){ + if( p->nFrac>0 ){ + if( iExp<=p->nFrac ){ + p->nFrac -= iExp; + iExp = 0; + }else{ + iExp -= p->nFrac; + p->nFrac = 0; + } + } + if( iExp>0 ){ + p->a = sqlite3_realloc64(p->a, p->nDigit + iExp + 1 ); + if( p->a==0 ) goto new_no_mem; + memset(p->a+p->nDigit, 0, iExp); + p->nDigit += iExp; + } + }else if( iExp<0 ){ + int nExtra; + iExp = -iExp; + nExtra = p->nDigit - p->nFrac - 1; + if( nExtra ){ + if( nExtra>=iExp ){ + p->nFrac += iExp; + iExp = 0; + }else{ + iExp -= nExtra; + p->nFrac = p->nDigit - 1; + } + } + if( iExp>0 ){ + p->a = sqlite3_realloc64(p->a, p->nDigit + iExp + 1 ); + if( p->a==0 ) goto new_no_mem; + memmove(p->a+iExp, p->a, p->nDigit); + memset(p->a, 0, iExp); + p->nDigit += iExp; + p->nFrac += iExp; + } + } + return p; + +new_no_mem: + if( pCtx ) sqlite3_result_error_nomem(pCtx); + sqlite3_free(p); + return 0; +} + +/* +** Make the given Decimal the result. +*/ +static void decimal_result(sqlite3_context *pCtx, Decimal *p){ + char *z; + int i, j; + int n; + if( p==0 || p->oom ){ + sqlite3_result_error_nomem(pCtx); + return; + } + if( p->isNull ){ + sqlite3_result_null(pCtx); + return; + } + z = sqlite3_malloc( p->nDigit+4 ); + if( z==0 ){ + sqlite3_result_error_nomem(pCtx); + return; + } + i = 0; + if( p->nDigit==0 || (p->nDigit==1 && p->a[0]==0) ){ + p->sign = 0; + } + if( p->sign ){ + z[0] = '-'; + i = 1; + } + n = p->nDigit - p->nFrac; + if( n<=0 ){ + z[i++] = '0'; + } + j = 0; + while( n>1 && p->a[j]==0 ){ + j++; + n--; + } + while( n>0 ){ + z[i++] = p->a[j] + '0'; + j++; + n--; + } + if( p->nFrac ){ + z[i++] = '.'; + do{ + z[i++] = p->a[j] + '0'; + j++; + }while( jnDigit ); + } + z[i] = 0; + sqlite3_result_text(pCtx, z, i, sqlite3_free); +} + +/* +** SQL Function: decimal(X) +** +** Convert input X into decimal and then back into text +*/ +static void decimalFunc( + sqlite3_context *context, + int argc, + sqlite3_value **argv +){ + Decimal *p = decimal_new(context, argv[0], 0, 0); + UNUSED_PARAMETER(argc); + decimal_result(context, p); + decimal_free(p); +} + +/* +** Compare to Decimal objects. Return negative, 0, or positive if the +** first object is less than, equal to, or greater than the second. +** +** Preconditions for this routine: +** +** pA!=0 +** pA->isNull==0 +** pB!=0 +** pB->isNull==0 +*/ +static int decimal_cmp(const Decimal *pA, const Decimal *pB){ + int nASig, nBSig, rc, n; + if( pA->sign!=pB->sign ){ + return pA->sign ? -1 : +1; + } + if( pA->sign ){ + const Decimal *pTemp = pA; + pA = pB; + pB = pTemp; + } + nASig = pA->nDigit - pA->nFrac; + nBSig = pB->nDigit - pB->nFrac; + if( nASig!=nBSig ){ + return nASig - nBSig; + } + n = pA->nDigit; + if( n>pB->nDigit ) n = pB->nDigit; + rc = memcmp(pA->a, pB->a, n); + if( rc==0 ){ + rc = pA->nDigit - pB->nDigit; + } + return rc; +} + +/* +** SQL Function: decimal_cmp(X, Y) +** +** Return negative, zero, or positive if X is less then, equal to, or +** greater than Y. +*/ +static void decimalCmpFunc( + sqlite3_context *context, + int argc, + sqlite3_value **argv +){ + Decimal *pA = 0, *pB = 0; + int rc; + + UNUSED_PARAMETER(argc); + pA = decimal_new(context, argv[0], 0, 0); + if( pA==0 || pA->isNull ) goto cmp_done; + pB = decimal_new(context, argv[1], 0, 0); + if( pB==0 || pB->isNull ) goto cmp_done; + rc = decimal_cmp(pA, pB); + if( rc<0 ) rc = -1; + else if( rc>0 ) rc = +1; + sqlite3_result_int(context, rc); +cmp_done: + decimal_free(pA); + decimal_free(pB); +} + +/* +** Expand the Decimal so that it has a least nDigit digits and nFrac +** digits to the right of the decimal point. +*/ +static void decimal_expand(Decimal *p, int nDigit, int nFrac){ + int nAddSig; + int nAddFrac; + if( p==0 ) return; + nAddFrac = nFrac - p->nFrac; + nAddSig = (nDigit - p->nDigit) - nAddFrac; + if( nAddFrac==0 && nAddSig==0 ) return; + p->a = sqlite3_realloc64(p->a, nDigit+1); + if( p->a==0 ){ + p->oom = 1; + return; + } + if( nAddSig ){ + memmove(p->a+nAddSig, p->a, p->nDigit); + memset(p->a, 0, nAddSig); + p->nDigit += nAddSig; + } + if( nAddFrac ){ + memset(p->a+p->nDigit, 0, nAddFrac); + p->nDigit += nAddFrac; + p->nFrac += nAddFrac; + } +} + +/* +** Add the value pB into pA. +** +** Both pA and pB might become denormalized by this routine. +*/ +static void decimal_add(Decimal *pA, Decimal *pB){ + int nSig, nFrac, nDigit; + int i, rc; + if( pA==0 ){ + return; + } + if( pA->oom || pB==0 || pB->oom ){ + pA->oom = 1; + return; + } + if( pA->isNull || pB->isNull ){ + pA->isNull = 1; + return; + } + nSig = pA->nDigit - pA->nFrac; + if( nSig && pA->a[0]==0 ) nSig--; + if( nSignDigit-pB->nFrac ){ + nSig = pB->nDigit - pB->nFrac; + } + nFrac = pA->nFrac; + if( nFracnFrac ) nFrac = pB->nFrac; + nDigit = nSig + nFrac + 1; + decimal_expand(pA, nDigit, nFrac); + decimal_expand(pB, nDigit, nFrac); + if( pA->oom || pB->oom ){ + pA->oom = 1; + }else{ + if( pA->sign==pB->sign ){ + int carry = 0; + for(i=nDigit-1; i>=0; i--){ + int x = pA->a[i] + pB->a[i] + carry; + if( x>=10 ){ + carry = 1; + pA->a[i] = x - 10; + }else{ + carry = 0; + pA->a[i] = x; + } + } + }else{ + signed char *aA, *aB; + int borrow = 0; + rc = memcmp(pA->a, pB->a, nDigit); + if( rc<0 ){ + aA = pB->a; + aB = pA->a; + pA->sign = !pA->sign; + }else{ + aA = pA->a; + aB = pB->a; + } + for(i=nDigit-1; i>=0; i--){ + int x = aA[i] - aB[i] - borrow; + if( x<0 ){ + pA->a[i] = x+10; + borrow = 1; + }else{ + pA->a[i] = x; + borrow = 0; + } + } + } + } +} + +/* +** Compare text in decimal order. +*/ +static int decimalCollFunc( + void *notUsed, + int nKey1, const void *pKey1, + int nKey2, const void *pKey2 +){ + const unsigned char *zA = (const unsigned char*)pKey1; + const unsigned char *zB = (const unsigned char*)pKey2; + Decimal *pA = decimal_new(0, 0, nKey1, zA); + Decimal *pB = decimal_new(0, 0, nKey2, zB); + int rc; + UNUSED_PARAMETER(notUsed); + if( pA==0 || pB==0 ){ + rc = 0; + }else{ + rc = decimal_cmp(pA, pB); + } + decimal_free(pA); + decimal_free(pB); + return rc; +} + + +/* +** SQL Function: decimal_add(X, Y) +** decimal_sub(X, Y) +** +** Return the sum or difference of X and Y. +*/ +static void decimalAddFunc( + sqlite3_context *context, + int argc, + sqlite3_value **argv +){ + Decimal *pA = decimal_new(context, argv[0], 0, 0); + Decimal *pB = decimal_new(context, argv[1], 0, 0); + UNUSED_PARAMETER(argc); + decimal_add(pA, pB); + decimal_result(context, pA); + decimal_free(pA); + decimal_free(pB); +} +static void decimalSubFunc( + sqlite3_context *context, + int argc, + sqlite3_value **argv +){ + Decimal *pA = decimal_new(context, argv[0], 0, 0); + Decimal *pB = decimal_new(context, argv[1], 0, 0); + UNUSED_PARAMETER(argc); + if( pB ){ + pB->sign = !pB->sign; + decimal_add(pA, pB); + decimal_result(context, pA); + } + decimal_free(pA); + decimal_free(pB); +} + +/* Aggregate funcion: decimal_sum(X) +** +** Works like sum() except that it uses decimal arithmetic for unlimited +** precision. +*/ +static void decimalSumStep( + sqlite3_context *context, + int argc, + sqlite3_value **argv +){ + Decimal *p; + Decimal *pArg; + UNUSED_PARAMETER(argc); + p = sqlite3_aggregate_context(context, sizeof(*p)); + if( p==0 ) return; + if( !p->isInit ){ + p->isInit = 1; + p->a = sqlite3_malloc(2); + if( p->a==0 ){ + p->oom = 1; + }else{ + p->a[0] = 0; + } + p->nDigit = 1; + p->nFrac = 0; + } + if( sqlite3_value_type(argv[0])==SQLITE_NULL ) return; + pArg = decimal_new(context, argv[0], 0, 0); + decimal_add(p, pArg); + decimal_free(pArg); +} +static void decimalSumInverse( + sqlite3_context *context, + int argc, + sqlite3_value **argv +){ + Decimal *p; + Decimal *pArg; + UNUSED_PARAMETER(argc); + p = sqlite3_aggregate_context(context, sizeof(*p)); + if( p==0 ) return; + if( sqlite3_value_type(argv[0])==SQLITE_NULL ) return; + pArg = decimal_new(context, argv[0], 0, 0); + if( pArg ) pArg->sign = !pArg->sign; + decimal_add(p, pArg); + decimal_free(pArg); +} +static void decimalSumValue(sqlite3_context *context){ + Decimal *p = sqlite3_aggregate_context(context, 0); + if( p==0 ) return; + decimal_result(context, p); +} +static void decimalSumFinalize(sqlite3_context *context){ + Decimal *p = sqlite3_aggregate_context(context, 0); + if( p==0 ) return; + decimal_result(context, p); + decimal_clear(p); +} + +/* +** SQL Function: decimal_mul(X, Y) +** +** Return the product of X and Y. +** +** All significant digits after the decimal point are retained. +** Trailing zeros after the decimal point are omitted as long as +** the number of digits after the decimal point is no less than +** either the number of digits in either input. +*/ +static void decimalMulFunc( + sqlite3_context *context, + int argc, + sqlite3_value **argv +){ + Decimal *pA = decimal_new(context, argv[0], 0, 0); + Decimal *pB = decimal_new(context, argv[1], 0, 0); + signed char *acc = 0; + int i, j, k; + int minFrac; + UNUSED_PARAMETER(argc); + if( pA==0 || pA->oom || pA->isNull + || pB==0 || pB->oom || pB->isNull + ){ + goto mul_end; + } + acc = sqlite3_malloc64( pA->nDigit + pB->nDigit + 2 ); + if( acc==0 ){ + sqlite3_result_error_nomem(context); + goto mul_end; + } + memset(acc, 0, pA->nDigit + pB->nDigit + 2); + minFrac = pA->nFrac; + if( pB->nFracnFrac; + for(i=pA->nDigit-1; i>=0; i--){ + signed char f = pA->a[i]; + int carry = 0, x; + for(j=pB->nDigit-1, k=i+j+3; j>=0; j--, k--){ + x = acc[k] + f*pB->a[j] + carry; + acc[k] = x%10; + carry = x/10; + } + x = acc[k] + carry; + acc[k] = x%10; + acc[k-1] += x/10; + } + sqlite3_free(pA->a); + pA->a = acc; + acc = 0; + pA->nDigit += pB->nDigit + 2; + pA->nFrac += pB->nFrac; + pA->sign ^= pB->sign; + while( pA->nFrac>minFrac && pA->a[pA->nDigit-1]==0 ){ + pA->nFrac--; + pA->nDigit--; + } + decimal_result(context, pA); + +mul_end: + sqlite3_free(acc); + decimal_free(pA); + decimal_free(pB); +} + +#ifdef _WIN32 + +#endif +int sqlite3_decimal_init( + sqlite3 *db, + char **pzErrMsg, + const sqlite3_api_routines *pApi +){ + int rc = SQLITE_OK; + static const struct { + const char *zFuncName; + int nArg; + void (*xFunc)(sqlite3_context*,int,sqlite3_value**); + } aFunc[] = { + { "decimal", 1, decimalFunc }, + { "decimal_cmp", 2, decimalCmpFunc }, + { "decimal_add", 2, decimalAddFunc }, + { "decimal_sub", 2, decimalSubFunc }, + { "decimal_mul", 2, decimalMulFunc }, + }; + unsigned int i; + (void)pzErrMsg; /* Unused parameter */ + + SQLITE_EXTENSION_INIT2(pApi); + + for(i=0; i 'ieee754(2,0)' +** ieee754(45.25) -> 'ieee754(181,-2)' +** ieee754(2, 0) -> 2.0 +** ieee754(181, -2) -> 45.25 +** +** Two additional functions break apart the one-argument ieee754() +** result into separate integer values: +** +** ieee754_mantissa(45.25) -> 181 +** ieee754_exponent(45.25) -> -2 +** +** These functions convert binary64 numbers into blobs and back again. +** +** ieee754_from_blob(x'3ff0000000000000') -> 1.0 +** ieee754_to_blob(1.0) -> x'3ff0000000000000' +** +** In all single-argument functions, if the argument is an 8-byte blob +** then that blob is interpreted as a big-endian binary64 value. +** +** +** EXACT DECIMAL REPRESENTATION OF BINARY64 VALUES +** ----------------------------------------------- +** +** This extension in combination with the separate 'decimal' extension +** can be used to compute the exact decimal representation of binary64 +** values. To begin, first compute a table of exponent values: +** +** CREATE TABLE pow2(x INTEGER PRIMARY KEY, v TEXT); +** WITH RECURSIVE c(x,v) AS ( +** VALUES(0,'1') +** UNION ALL +** SELECT x+1, decimal_mul(v,'2') FROM c WHERE x+1<=971 +** ) INSERT INTO pow2(x,v) SELECT x, v FROM c; +** WITH RECURSIVE c(x,v) AS ( +** VALUES(-1,'0.5') +** UNION ALL +** SELECT x-1, decimal_mul(v,'0.5') FROM c WHERE x-1>=-1075 +** ) INSERT INTO pow2(x,v) SELECT x, v FROM c; +** +** Then, to compute the exact decimal representation of a floating +** point value (the value 47.49 is used in the example) do: +** +** WITH c(n) AS (VALUES(47.49)) +** ---------------^^^^^---- Replace with whatever you want +** SELECT decimal_mul(ieee754_mantissa(c.n),pow2.v) +** FROM pow2, c WHERE pow2.x=ieee754_exponent(c.n); +** +** Here is a query to show various boundry values for the binary64 +** number format: +** +** WITH c(name,bin) AS (VALUES +** ('minimum positive value', x'0000000000000001'), +** ('maximum subnormal value', x'000fffffffffffff'), +** ('mininum positive nornal value', x'0010000000000000'), +** ('maximum value', x'7fefffffffffffff')) +** SELECT c.name, decimal_mul(ieee754_mantissa(c.bin),pow2.v) +** FROM pow2, c WHERE pow2.x=ieee754_exponent(c.bin); +** +*/ +/* #include "sqlite3ext.h" */ +SQLITE_EXTENSION_INIT1 +#include +#include + +/* Mark a function parameter as unused, to suppress nuisance compiler +** warnings. */ +#ifndef UNUSED_PARAMETER +# define UNUSED_PARAMETER(X) (void)(X) +#endif + +/* +** Implementation of the ieee754() function +*/ +static void ieee754func( + sqlite3_context *context, + int argc, + sqlite3_value **argv +){ + if( argc==1 ){ + sqlite3_int64 m, a; + double r; + int e; + int isNeg; + char zResult[100]; + assert( sizeof(m)==sizeof(r) ); + if( sqlite3_value_type(argv[0])==SQLITE_BLOB + && sqlite3_value_bytes(argv[0])==sizeof(r) + ){ + const unsigned char *x = sqlite3_value_blob(argv[0]); + unsigned int i; + sqlite3_uint64 v = 0; + for(i=0; i>52; + m = a & ((((sqlite3_int64)1)<<52)-1); + if( e==0 ){ + m <<= 1; + }else{ + m |= ((sqlite3_int64)1)<<52; + } + while( e<1075 && m>0 && (m&1)==0 ){ + m >>= 1; + e++; + } + if( isNeg ) m = -m; + } + switch( *(int*)sqlite3_user_data(context) ){ + case 0: + sqlite3_snprintf(sizeof(zResult), zResult, "ieee754(%lld,%d)", + m, e-1075); + sqlite3_result_text(context, zResult, -1, SQLITE_TRANSIENT); + break; + case 1: + sqlite3_result_int64(context, m); + break; + case 2: + sqlite3_result_int(context, e-1075); + break; + } + }else{ + sqlite3_int64 m, e, a; + double r; + int isNeg = 0; + m = sqlite3_value_int64(argv[0]); + e = sqlite3_value_int64(argv[1]); + + /* Limit the range of e. Ticket 22dea1cfdb9151e4 2021-03-02 */ + if( e>10000 ){ + e = 10000; + }else if( e<-10000 ){ + e = -10000; + } + + if( m<0 ){ + isNeg = 1; + m = -m; + if( m<0 ) return; + }else if( m==0 && e>-1000 && e<1000 ){ + sqlite3_result_double(context, 0.0); + return; + } + while( (m>>32)&0xffe00000 ){ + m >>= 1; + e++; + } + while( m!=0 && ((m>>32)&0xfff00000)==0 ){ + m <<= 1; + e--; + } + e += 1075; + if( e<=0 ){ + /* Subnormal */ + if( 1-e >= 64 ){ + m = 0; + }else{ + m >>= 1-e; + } + e = 0; + }else if( e>0x7ff ){ + e = 0x7ff; + } + a = m & ((((sqlite3_int64)1)<<52)-1); + a |= e<<52; + if( isNeg ) a |= ((sqlite3_uint64)1)<<63; + memcpy(&r, &a, sizeof(r)); + sqlite3_result_double(context, r); + } +} + +/* +** Functions to convert between blobs and floats. +*/ +static void ieee754func_from_blob( + sqlite3_context *context, + int argc, + sqlite3_value **argv +){ + UNUSED_PARAMETER(argc); + if( sqlite3_value_type(argv[0])==SQLITE_BLOB + && sqlite3_value_bytes(argv[0])==sizeof(double) + ){ + double r; + const unsigned char *x = sqlite3_value_blob(argv[0]); + unsigned int i; + sqlite3_uint64 v = 0; + for(i=0; i>= 8; + } + sqlite3_result_blob(context, a, sizeof(r), SQLITE_TRANSIENT); + } +} + + +#ifdef _WIN32 + +#endif +int sqlite3_ieee_init( + sqlite3 *db, + char **pzErrMsg, + const sqlite3_api_routines *pApi +){ + static const struct { + char *zFName; + int nArg; + int iAux; + void (*xFunc)(sqlite3_context*,int,sqlite3_value**); + } aFunc[] = { + { "ieee754", 1, 0, ieee754func }, + { "ieee754", 2, 0, ieee754func }, + { "ieee754_mantissa", 1, 1, ieee754func }, + { "ieee754_exponent", 1, 2, ieee754func }, + { "ieee754_to_blob", 1, 0, ieee754func_to_blob }, + { "ieee754_from_blob", 1, 0, ieee754func_from_blob }, + + }; + unsigned int i; + int rc = SQLITE_OK; + SQLITE_EXTENSION_INIT2(pApi); + (void)pzErrMsg; /* Unused parameter */ + for(i=0; i