sptc 0.0.0 → 0.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +72 -0
- package/bin/sptc.js +25 -0
- package/bin/sptcd.js +39 -0
- package/dist/httpServer.js +147 -0
- package/dist/webpack.loader.js +48 -0
- package/engine/compiler-macro.js +237 -0
- package/engine/compiler.js +81 -0
- package/engine/index.js +64 -0
- package/engine/interpreter-macro.js +77 -0
- package/engine/interpreter.js +291 -0
- package/package.json +8 -1
- package/tests/bin.sh +9 -0
- package/tests/engine/TestModel.sjs +7 -0
- package/tests/engine/a.sjs +58 -0
- package/tests/engine/ee.sjs +17 -0
- package/tests/engine/m1.js +20 -0
- package/tests/engine/ppp.sjs +6 -0
- package/tests/engine/test-macro.js +16 -0
- package/tests/engine/test.js +33 -0
- package/tests/www/index.sjs +17 -0
- package/utils/base.js +65 -0
- package/utils/compileFile.js +38 -0
- package/utils/fpm.js +15 -0
- package/utils/index.js +6 -0
package/README.md
CHANGED
|
@@ -1,3 +1,75 @@
|
|
|
1
1
|
# Simple Pretreat Toolkit CLI
|
|
2
2
|
|
|
3
3
|
This project is developing..
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
### sptc
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
Usage: sptc [options]
|
|
11
|
+
|
|
12
|
+
sptc
|
|
13
|
+
|
|
14
|
+
Options:
|
|
15
|
+
-V, --version output the version number
|
|
16
|
+
-f, --filename <string> Sptc filename.
|
|
17
|
+
-d, --defines <string> Macro definition switches. If you want to use multiple switches, separate them with ",".
|
|
18
|
+
(default: "")
|
|
19
|
+
-h, --help display help for command
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
### sptcd
|
|
23
|
+
```
|
|
24
|
+
Usage: sptc http server [options]
|
|
25
|
+
|
|
26
|
+
A simple http server
|
|
27
|
+
|
|
28
|
+
Options:
|
|
29
|
+
-V, --version output the version number
|
|
30
|
+
-p, --port <number> Serve port. (default: 9090)
|
|
31
|
+
-l, --locally Only accepts local connections.
|
|
32
|
+
-w, --workdir <string> Specify the working directory. (default: ".")
|
|
33
|
+
-r, --router <string> Specify a file as the router entry. If specified, all requests will pass to this file.
|
|
34
|
+
-e, --exts <string> Specify the valid extensions of executable sptc files. (default: ".sjs,.shtml")
|
|
35
|
+
-n, --workers <number> Workers count. (default: 1)
|
|
36
|
+
-s, --slient Slient mode.
|
|
37
|
+
-h, --help display help for command
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
### webpack loader
|
|
41
|
+
```
|
|
42
|
+
Usage:
|
|
43
|
+
|
|
44
|
+
Webpack configuration:
|
|
45
|
+
|
|
46
|
+
{
|
|
47
|
+
test: /\.(jsx?)$/,
|
|
48
|
+
exclude: /node_modules/,
|
|
49
|
+
options: {
|
|
50
|
+
file: path.resolve(__dirname+'/sptc.inject.js'),
|
|
51
|
+
},
|
|
52
|
+
loader: 'sptc/dist/webpack.loader.js',
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
**sptc.inject.js**
|
|
58
|
+
|
|
59
|
+
module.exports={
|
|
60
|
+
EXTENDS: (ctx)=>({
|
|
61
|
+
get_module_dir: _=>{
|
|
62
|
+
return ctx.fn.replace(/\\+/g, '/').replace(/(^.*?src\/modules\/[^/]+).*$/, '$1')+'/'
|
|
63
|
+
},
|
|
64
|
+
IS_NODE_TARGET: ctx.webpackLoaderThis.target==='node',
|
|
65
|
+
}),
|
|
66
|
+
TPLS: [
|
|
67
|
+
/\.jsx?$/, ctx=>{
|
|
68
|
+
let {str, fn}=ctx
|
|
69
|
+
return `console.log("// ${fn}")\n`+str
|
|
70
|
+
}
|
|
71
|
+
],
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
```
|
package/bin/sptc.js
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const {executeSptcFile}=require('../engine')
|
|
4
|
+
const {Command}=require('commander')
|
|
5
|
+
const version=require('../package.json').version
|
|
6
|
+
const program=new Command()
|
|
7
|
+
.name(`sptc`)
|
|
8
|
+
.description(`sptc`)
|
|
9
|
+
.version(version)
|
|
10
|
+
.requiredOption('-f, --filename <string>', 'Sptc filename.')
|
|
11
|
+
.option('-d, --defines <string>', 'Macro definition switches. If you want to use multiple switches, separate them with ",".', '')
|
|
12
|
+
.action(({
|
|
13
|
+
filename,
|
|
14
|
+
defines,
|
|
15
|
+
})=>{
|
|
16
|
+
executeSptcFile(filename, {}, {
|
|
17
|
+
write: x=>process.stdout.write(x),
|
|
18
|
+
onError: e=>console.log(e),
|
|
19
|
+
__DEV__: true,
|
|
20
|
+
macroOption: {
|
|
21
|
+
defs: defines.split(',').map(x=>x.trim()).filter(x=>x),
|
|
22
|
+
},
|
|
23
|
+
})
|
|
24
|
+
})
|
|
25
|
+
.parse()
|
package/bin/sptcd.js
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const {FastCGI, FastCGI_FPM}=require('../dist/httpServer')
|
|
4
|
+
const {Command}=require('commander')
|
|
5
|
+
const version=require('../package.json').version
|
|
6
|
+
const program=new Command()
|
|
7
|
+
.name(`sptc http server`)
|
|
8
|
+
.description(`A simple http server`)
|
|
9
|
+
.version(version)
|
|
10
|
+
.requiredOption('-p, --port <number>', 'Serve port.', 9090)
|
|
11
|
+
.option('-l, --locally', 'Only accepts local connections.')
|
|
12
|
+
.option('-w, --workdir <string>', 'Specify the working directory.', '.')
|
|
13
|
+
.option('-r, --router <string>', 'Specify a file as the router entry. If specified, all requests will pass to this file.')
|
|
14
|
+
.option('-e, --exts <string>', 'Specify the valid extensions of executable sptc files.', '.sjs,.shtml')
|
|
15
|
+
.option('-n, --workers <number>', 'Workers count.', 1)
|
|
16
|
+
.option('-s, --slient', 'Slient mode.')
|
|
17
|
+
.action(({
|
|
18
|
+
port,
|
|
19
|
+
locally,
|
|
20
|
+
workdir,
|
|
21
|
+
router,
|
|
22
|
+
exts,
|
|
23
|
+
workers,
|
|
24
|
+
slient,
|
|
25
|
+
})=>{
|
|
26
|
+
const argv=[port, locally, {
|
|
27
|
+
serverDir: workdir,
|
|
28
|
+
routerEntry: router,
|
|
29
|
+
exts: exts.split(',').map(x=>x.trim()).filter(x=>x),
|
|
30
|
+
debug: true,
|
|
31
|
+
slientMode: slient,
|
|
32
|
+
}]
|
|
33
|
+
if(workers>1) {
|
|
34
|
+
FastCGI_FPM(workers, ...argv)
|
|
35
|
+
}else{
|
|
36
|
+
FastCGI(...argv)
|
|
37
|
+
}
|
|
38
|
+
})
|
|
39
|
+
.parse()
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
const {
|
|
2
|
+
fpm,
|
|
3
|
+
safe_path,
|
|
4
|
+
fileExists,
|
|
5
|
+
getExtension,
|
|
6
|
+
getLocalIpv4Addresses,
|
|
7
|
+
}=require('../utils')
|
|
8
|
+
const {executeSptcFile}=require('../engine')
|
|
9
|
+
|
|
10
|
+
process.on('uncaughtException', e=>{
|
|
11
|
+
console.log('uncaughtException:', e)
|
|
12
|
+
})
|
|
13
|
+
|
|
14
|
+
function FastCGI_FPM(n, port, locally, option={}) {
|
|
15
|
+
option.env={
|
|
16
|
+
RUNTIME: 'FastCGI_FPM',
|
|
17
|
+
}
|
|
18
|
+
fpm(n, _=>FastCGI(port, locally, option))
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function FastCGI(port, locally, option) {
|
|
22
|
+
const http=require('http')
|
|
23
|
+
const path=require('path')
|
|
24
|
+
const {
|
|
25
|
+
serverDir='.',
|
|
26
|
+
routerEntry=null,
|
|
27
|
+
slientMode=false,
|
|
28
|
+
..._option
|
|
29
|
+
}=option
|
|
30
|
+
_option.env=_option.env || {
|
|
31
|
+
RUNTIME: 'FastCGI',
|
|
32
|
+
}
|
|
33
|
+
_option.srvDir=path.resolve(serverDir)
|
|
34
|
+
_option.router=routerEntry? safe_path(_option.srvDir, routerEntry): null
|
|
35
|
+
http.createServer((req, res)=>{
|
|
36
|
+
CGI(req, res, _option)
|
|
37
|
+
}).listen(port, locally? '127.0.0.1': '0.0.0.0', _=>{
|
|
38
|
+
console.log()
|
|
39
|
+
console.log('`sptc-http-server` has been launched with the following option:')
|
|
40
|
+
const o={
|
|
41
|
+
workdir: _option.srvDir,
|
|
42
|
+
routerEntry: _option.router,
|
|
43
|
+
}
|
|
44
|
+
if(locally) {
|
|
45
|
+
o.serve='127.0.0.1:'+port
|
|
46
|
+
}else{
|
|
47
|
+
o.serves=getLocalIpv4Addresses().map(x=>x+':'+port)
|
|
48
|
+
}
|
|
49
|
+
console.log(o)
|
|
50
|
+
console.log()
|
|
51
|
+
})
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function CGI(req, res, option) {
|
|
55
|
+
const {
|
|
56
|
+
srvDir,
|
|
57
|
+
exts=['.sjs', '.shtml'],
|
|
58
|
+
debug=false,
|
|
59
|
+
router=null,
|
|
60
|
+
env,
|
|
61
|
+
}=option
|
|
62
|
+
const [reqCtx, handler]=buildRequestContext(req, res, {srvDir, debug, env})
|
|
63
|
+
const file=reqCtx.$_REQUEST_FILE || {}
|
|
64
|
+
const fn=router || file.fullname
|
|
65
|
+
if(!fileExists(fn)) {
|
|
66
|
+
res.writeHead(404, {'content-type': 'text/plain'})
|
|
67
|
+
res.end(`\`${router || file.pathname}\` does not exist`)
|
|
68
|
+
return
|
|
69
|
+
}
|
|
70
|
+
const ext=getExtension(fn)
|
|
71
|
+
if(!exts.includes(ext)) {
|
|
72
|
+
res.writeHead(403, {'content-type': 'text/plain'})
|
|
73
|
+
res.end(`\`${ext}\` does not match`)
|
|
74
|
+
return
|
|
75
|
+
}
|
|
76
|
+
executeSptcFile(fn, reqCtx, handler)
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function buildRequestContext(req, res, {srvDir, debug, env}) {
|
|
80
|
+
const {fullname, pathname}=getRequestFile(req, srvDir)
|
|
81
|
+
const reqCtx={
|
|
82
|
+
$_RAW_REQUEST: req,
|
|
83
|
+
$_RAW_RESPONSE: res,
|
|
84
|
+
$_ENV: env,
|
|
85
|
+
$_REQUEST_FILE: {
|
|
86
|
+
fullname,
|
|
87
|
+
pathname,
|
|
88
|
+
},
|
|
89
|
+
$_DEBUG: debug,
|
|
90
|
+
$_WORKDIR: srvDir,
|
|
91
|
+
}
|
|
92
|
+
reqCtx.$_GLOBAL=reqCtx
|
|
93
|
+
|
|
94
|
+
const state={
|
|
95
|
+
statusCode: 200,
|
|
96
|
+
statusText: 'OK',
|
|
97
|
+
responseHeaders: {'content-type': 'text/html'},
|
|
98
|
+
error: null,
|
|
99
|
+
headerFlushed: false,
|
|
100
|
+
}
|
|
101
|
+
function flushHeader() {
|
|
102
|
+
if(state.headerFlushed) return;
|
|
103
|
+
state.headerFlushed=true
|
|
104
|
+
res.writeHead(state.statusCode, state.statusText, state.responseHeaders)
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
reqCtx.setStatus=(code, text)=>{
|
|
108
|
+
state.statusCode=200
|
|
109
|
+
if(text!==undefined) state.statusText=text
|
|
110
|
+
}
|
|
111
|
+
reqCtx.setResponseHeaders=headers=>{
|
|
112
|
+
Object.assign(state.responseHeaders, headers)
|
|
113
|
+
}
|
|
114
|
+
reqCtx.flushHeader=_=>{
|
|
115
|
+
flushHeader()
|
|
116
|
+
}
|
|
117
|
+
reqCtx.setDebug=x=>{
|
|
118
|
+
reqCtx.$_DEBUG=x
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
return [reqCtx, {
|
|
122
|
+
write: x=>{
|
|
123
|
+
flushHeader()
|
|
124
|
+
res.write(x)
|
|
125
|
+
},
|
|
126
|
+
end: _=>{
|
|
127
|
+
flushHeader()
|
|
128
|
+
res.end()
|
|
129
|
+
},
|
|
130
|
+
onError: e=>{
|
|
131
|
+
if(reqCtx.$_DEBUG) console.log(e)
|
|
132
|
+
},
|
|
133
|
+
}]
|
|
134
|
+
}
|
|
135
|
+
function getRequestFile(req, srvDir) {
|
|
136
|
+
const _url=require('url')
|
|
137
|
+
const {pathname}=_url.parse(req.url)
|
|
138
|
+
return {
|
|
139
|
+
fullname: safe_path(srvDir, pathname),
|
|
140
|
+
pathname,
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
module.exports={
|
|
145
|
+
FastCGI,
|
|
146
|
+
FastCGI_FPM,
|
|
147
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
const {executeSptcFileEx}=require('../engine')
|
|
2
|
+
const path=require(`path`)
|
|
3
|
+
|
|
4
|
+
module.exports=async function(str) {
|
|
5
|
+
let query=this.query || {}
|
|
6
|
+
if(this.query) {
|
|
7
|
+
const {file}=this.query
|
|
8
|
+
if(file) {
|
|
9
|
+
query=Object.assign({}, require(file))
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const {
|
|
14
|
+
EXTENDS=(wCtx=>({})),
|
|
15
|
+
TPLS=[],
|
|
16
|
+
DEFINES=[],
|
|
17
|
+
}=query
|
|
18
|
+
|
|
19
|
+
let wCtx={
|
|
20
|
+
str: str,
|
|
21
|
+
fn: path.resolve(this.resourcePath).replace(/\\+/g, '/'),
|
|
22
|
+
webpackLoaderThis: this,
|
|
23
|
+
}
|
|
24
|
+
const globals=Object.assign(EXTENDS(wCtx), {wCtx})
|
|
25
|
+
wCtx.runtimeGlobals=globals
|
|
26
|
+
|
|
27
|
+
let callback=this.async()
|
|
28
|
+
try{
|
|
29
|
+
const output=await executeSptcFileEx(wCtx.fn, globals, {
|
|
30
|
+
__DEV__: true,
|
|
31
|
+
macroOption: {
|
|
32
|
+
defs: DEFINES,
|
|
33
|
+
},
|
|
34
|
+
mockFileContent: wCtx.str,
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
wCtx.str=output
|
|
38
|
+
|
|
39
|
+
for(let i=0; i<TPLS.length; i+=2){
|
|
40
|
+
const [_match, _handler]=[TPLS[i], TPLS[i+1]]
|
|
41
|
+
if(wCtx.fn.match(_match)) wCtx.str=_handler(wCtx, globals)
|
|
42
|
+
}
|
|
43
|
+
callback(null, wCtx.str)
|
|
44
|
+
}catch(e) {
|
|
45
|
+
callback(e)
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
}
|
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
|
|
2
|
+
const vm=require('vm')
|
|
3
|
+
const {compileFile}=require('../utils')
|
|
4
|
+
|
|
5
|
+
const Tokens={
|
|
6
|
+
T_IFDEF: '#ifdef',
|
|
7
|
+
T_ELSE: '#else',
|
|
8
|
+
T_ENDIF: '#endif',
|
|
9
|
+
T_IFNDEF: '#ifndef',
|
|
10
|
+
|
|
11
|
+
T_DEF: '#def',
|
|
12
|
+
T_UNDEF: '#undef',
|
|
13
|
+
|
|
14
|
+
T_INCLUDE: '#include',
|
|
15
|
+
|
|
16
|
+
T_DEFINE: '#define',
|
|
17
|
+
T_CALL_DEFINE: '@',
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function lexer(content) {
|
|
21
|
+
const {
|
|
22
|
+
T_IFDEF,
|
|
23
|
+
T_ELSE,
|
|
24
|
+
T_ENDIF,
|
|
25
|
+
T_IFNDEF,
|
|
26
|
+
|
|
27
|
+
T_DEF,
|
|
28
|
+
T_UNDEF,
|
|
29
|
+
|
|
30
|
+
T_INCLUDE,
|
|
31
|
+
|
|
32
|
+
T_DEFINE,
|
|
33
|
+
T_CALL_DEFINE,
|
|
34
|
+
}=Tokens
|
|
35
|
+
|
|
36
|
+
const tokens=[]
|
|
37
|
+
const lines=content.split('\n')
|
|
38
|
+
let reStr='((?:'+[
|
|
39
|
+
T_IFDEF,
|
|
40
|
+
T_ELSE,
|
|
41
|
+
T_ENDIF,
|
|
42
|
+
T_IFNDEF,
|
|
43
|
+
T_DEF,
|
|
44
|
+
T_UNDEF,
|
|
45
|
+
T_INCLUDE,
|
|
46
|
+
T_DEFINE,
|
|
47
|
+
].join('|')+')\\b)((?:\\s+).+)?|(.+)'
|
|
48
|
+
const re=new RegExp('^'+reStr, 'g')
|
|
49
|
+
const subre=new RegExp(T_CALL_DEFINE+'([A-Za-z\\d_]+)(\\([^)]*?\\)|\\b)|(.)', 'g')
|
|
50
|
+
|
|
51
|
+
for(let i=0; i<lines.length; i++) {
|
|
52
|
+
if(!lines[i]) {
|
|
53
|
+
tokens.push({frags: true, str: '\n'})
|
|
54
|
+
continue
|
|
55
|
+
}
|
|
56
|
+
lines[i].replace(re, (_, tk, tk_params, subline)=>{
|
|
57
|
+
if(tk) {
|
|
58
|
+
tokens.push({tk, tk_params: (tk_params || '').trim()})
|
|
59
|
+
}else if(subline) {
|
|
60
|
+
subline.replace(subre, (_, call_define, call_define_params, frags)=>{
|
|
61
|
+
if(call_define) {
|
|
62
|
+
tokens.push({call_define, call_define_params, source: _})
|
|
63
|
+
}else if(frags) {
|
|
64
|
+
tokens.push({frags: true, str: frags})
|
|
65
|
+
}
|
|
66
|
+
})
|
|
67
|
+
tokens.push({frags: true, str: '\n'})
|
|
68
|
+
}
|
|
69
|
+
})
|
|
70
|
+
}
|
|
71
|
+
const v=tokens.pop()
|
|
72
|
+
if(!(v.frags===true && v.str==='\n')) {
|
|
73
|
+
tokens.push(v)
|
|
74
|
+
}
|
|
75
|
+
return tokens
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const O_IFDEF=Symbol()
|
|
79
|
+
const O_STR=Symbol()
|
|
80
|
+
const O_DEFINE_CONST=Symbol()
|
|
81
|
+
const O_DEFINE_CALL=Symbol()
|
|
82
|
+
const O_CALL_CONST=Symbol()
|
|
83
|
+
const O_CALL_DEFINE=Symbol()
|
|
84
|
+
const O_DEF=Symbol()
|
|
85
|
+
const O_INCLUDE=Symbol()
|
|
86
|
+
const O_UNDEF=Symbol()
|
|
87
|
+
function transformToAst(tokens) {
|
|
88
|
+
const {
|
|
89
|
+
T_IFDEF,
|
|
90
|
+
T_ELSE,
|
|
91
|
+
T_ENDIF,
|
|
92
|
+
T_IFNDEF,
|
|
93
|
+
|
|
94
|
+
T_DEF,
|
|
95
|
+
T_UNDEF,
|
|
96
|
+
|
|
97
|
+
T_INCLUDE,
|
|
98
|
+
|
|
99
|
+
T_DEFINE,
|
|
100
|
+
T_CALL_DEFINE,
|
|
101
|
+
}=Tokens
|
|
102
|
+
|
|
103
|
+
const tree=[]
|
|
104
|
+
const if_stacks=[]
|
|
105
|
+
function _get_if_stack() {
|
|
106
|
+
if(!if_stacks.length) return;
|
|
107
|
+
const p=if_stacks[if_stacks.length-1]
|
|
108
|
+
const {_push_consequent, _reverse, consequent, alternate}=p
|
|
109
|
+
let cd=[consequent, alternate]
|
|
110
|
+
if(_reverse) cd.reverse()
|
|
111
|
+
return cd[_push_consequent? 0: 1] // .push(if_statement)
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
for(let i=0; i<tokens.length; i++) {
|
|
115
|
+
const x=tokens[i]
|
|
116
|
+
if(x.tk===T_IFDEF || x.tk===T_IFNDEF) {
|
|
117
|
+
const if_statement={
|
|
118
|
+
type: O_IFDEF,
|
|
119
|
+
match: x.tk_params,
|
|
120
|
+
consequent: [],
|
|
121
|
+
alternate: [],
|
|
122
|
+
_push_consequent: true,
|
|
123
|
+
_reverse: x.tk===T_IFNDEF,
|
|
124
|
+
}
|
|
125
|
+
const _cur_if_stack=_get_if_stack()
|
|
126
|
+
_cur_if_stack && _cur_if_stack.push(if_statement)
|
|
127
|
+
if_stacks.push(if_statement)
|
|
128
|
+
}else if(x.tk===T_ELSE) {
|
|
129
|
+
const t=if_stacks[if_stacks.length-1]
|
|
130
|
+
if(!t || t.type!==O_IFDEF) throw new Error('not match '+T_ELSE)
|
|
131
|
+
t._push_consequent=false
|
|
132
|
+
}else if(x.tk===T_ENDIF) {
|
|
133
|
+
const t=if_stacks[if_stacks.length-1]
|
|
134
|
+
if(!t || t.type!==O_IFDEF) throw new Error('not match '+T_ENDIF)
|
|
135
|
+
const p=if_stacks.pop()
|
|
136
|
+
if(!if_stacks.length) {
|
|
137
|
+
tree.push(p)
|
|
138
|
+
}
|
|
139
|
+
}else{
|
|
140
|
+
let d={}
|
|
141
|
+
if(x.tk===T_DEF) {
|
|
142
|
+
Object.assign(d, {
|
|
143
|
+
type: O_DEF,
|
|
144
|
+
def: x.tk_params,
|
|
145
|
+
})
|
|
146
|
+
}else if(x.tk===T_UNDEF) {
|
|
147
|
+
Object.assign(d, {
|
|
148
|
+
type: O_UNDEF,
|
|
149
|
+
def: x.tk_params,
|
|
150
|
+
})
|
|
151
|
+
}else if(x.tk===T_INCLUDE) {
|
|
152
|
+
Object.assign(d, {
|
|
153
|
+
type: O_INCLUDE,
|
|
154
|
+
include: x.tk_params,
|
|
155
|
+
})
|
|
156
|
+
}else if(x.tk===T_DEFINE) {
|
|
157
|
+
const re_define_func=/^([A-Za-z\d_]+)\(([^)]+)\)\s+(.+)/
|
|
158
|
+
const re_define_const=/^([A-Za-z\d_]+)\s+(.+)/
|
|
159
|
+
const matched_func=x.tk_params.match(re_define_func)
|
|
160
|
+
const matched_const=x.tk_params.match(re_define_const)
|
|
161
|
+
if(matched_func) {
|
|
162
|
+
const [, funcname, argv, func_body]=matched_func
|
|
163
|
+
Object.assign(d, {
|
|
164
|
+
type: O_DEFINE_CALL,
|
|
165
|
+
fname: funcname,
|
|
166
|
+
fcall: new vm.Script(`(${argv})=>(${func_body})`).runInNewContext({require}),
|
|
167
|
+
})
|
|
168
|
+
}else if(matched_const) {
|
|
169
|
+
const [, constant, value]=matched_const
|
|
170
|
+
Object.assign(d, {
|
|
171
|
+
type: O_DEFINE_CONST,
|
|
172
|
+
cname: constant,
|
|
173
|
+
cvalue: value+'',
|
|
174
|
+
})
|
|
175
|
+
}else{
|
|
176
|
+
throw new Error('unsupported token: '+JSON.stringify(x))
|
|
177
|
+
}
|
|
178
|
+
}else if(x.call_define) {
|
|
179
|
+
if(x.call_define_params) {
|
|
180
|
+
const argv=x.call_define_params.substr(1, x.call_define_params.length-2).split(',').map(a=>a.trim())
|
|
181
|
+
Object.assign(d, {
|
|
182
|
+
type: O_CALL_DEFINE,
|
|
183
|
+
call: x.call_define,
|
|
184
|
+
argv,
|
|
185
|
+
source: x.source,
|
|
186
|
+
})
|
|
187
|
+
}else{
|
|
188
|
+
Object.assign(d, {
|
|
189
|
+
type: O_CALL_CONST,
|
|
190
|
+
call: x.call_define,
|
|
191
|
+
source: x.source,
|
|
192
|
+
})
|
|
193
|
+
}
|
|
194
|
+
}else if(x.frags) {
|
|
195
|
+
Object.assign(d, {
|
|
196
|
+
type: O_STR,
|
|
197
|
+
str: x.str,
|
|
198
|
+
})
|
|
199
|
+
}else{
|
|
200
|
+
throw new Error('unsupported token: '+JSON.stringify(x))
|
|
201
|
+
}
|
|
202
|
+
const ad=_get_if_stack() || tree
|
|
203
|
+
const _f=ad[ad.length-1]
|
|
204
|
+
if(d.type===O_STR && _f && _f.type===O_STR) {
|
|
205
|
+
_f.str+=d.str
|
|
206
|
+
}else{
|
|
207
|
+
ad.push(d)
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
return tree
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
const caches={}
|
|
215
|
+
function compileSptcMacroFile(filename, mockFileContent) {
|
|
216
|
+
return compileFile(filename, {
|
|
217
|
+
customCache: caches,
|
|
218
|
+
compileFunc: content=>transformToAst(lexer(content)),
|
|
219
|
+
mockFileContent,
|
|
220
|
+
})
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
module.exports={
|
|
225
|
+
compileSptcMacroFile,
|
|
226
|
+
MACRO_TYPES: {
|
|
227
|
+
O_IFDEF,
|
|
228
|
+
O_STR,
|
|
229
|
+
O_DEFINE_CONST,
|
|
230
|
+
O_DEFINE_CALL,
|
|
231
|
+
O_CALL_CONST,
|
|
232
|
+
O_CALL_DEFINE,
|
|
233
|
+
O_DEF,
|
|
234
|
+
O_INCLUDE,
|
|
235
|
+
O_UNDEF,
|
|
236
|
+
},
|
|
237
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
|
|
2
|
+
const vm=require('vm')
|
|
3
|
+
const path=require('path')
|
|
4
|
+
|
|
5
|
+
const {compileFile}=require('../utils')
|
|
6
|
+
|
|
7
|
+
const T_JS=Symbol()
|
|
8
|
+
const T_TEXT=Symbol()
|
|
9
|
+
function lexer(content) {
|
|
10
|
+
const FLAG_OPEN='<?js'
|
|
11
|
+
const FLAG_CLOSE='?>'
|
|
12
|
+
const tokens=[]
|
|
13
|
+
const _t_flags=[]
|
|
14
|
+
let c1=-1, c2=-1
|
|
15
|
+
function _append_token(tk, left, right) {
|
|
16
|
+
const tk_params=content.substr(left, right-left)
|
|
17
|
+
if(!tk_params.length) return;
|
|
18
|
+
tokens.push({tk, tk_params})
|
|
19
|
+
}
|
|
20
|
+
for(;;) {
|
|
21
|
+
c1=content.indexOf(FLAG_OPEN, c1)
|
|
22
|
+
c2=content.indexOf(FLAG_CLOSE, c2)
|
|
23
|
+
if(c1===-1 && c1===-1) break
|
|
24
|
+
if(c1>-1) {
|
|
25
|
+
_t_flags.push({fk: FLAG_OPEN, idx: c1})
|
|
26
|
+
c1+=FLAG_OPEN.length
|
|
27
|
+
}
|
|
28
|
+
if(c2>-1) {
|
|
29
|
+
_t_flags.push({fk: FLAG_CLOSE, idx: c2})
|
|
30
|
+
c2+=FLAG_CLOSE.length
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
if(_t_flags.length) {
|
|
34
|
+
const p=_t_flags[_t_flags.length-1]
|
|
35
|
+
if(p.fk!==FLAG_CLOSE) _t_flags.push({
|
|
36
|
+
fk: FLAG_CLOSE,
|
|
37
|
+
idx: content.length,
|
|
38
|
+
})
|
|
39
|
+
for(let i=0; i<_t_flags.length; i+=2) {
|
|
40
|
+
_append_token(T_TEXT, i>0? _t_flags[i-1].idx+FLAG_CLOSE.length: 0, _t_flags[i].idx)
|
|
41
|
+
_append_token(T_JS, _t_flags[i].idx+FLAG_OPEN.length, _t_flags[i+1].idx)
|
|
42
|
+
}
|
|
43
|
+
_append_token(T_TEXT, _t_flags[_t_flags.length-1].idx+FLAG_CLOSE.length, content.length)
|
|
44
|
+
}else{
|
|
45
|
+
tokens.push({
|
|
46
|
+
tk: T_TEXT,
|
|
47
|
+
tk_params: content,
|
|
48
|
+
})
|
|
49
|
+
}
|
|
50
|
+
return tokens
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function transformToAst(tokens, filename) {
|
|
54
|
+
let ret=''
|
|
55
|
+
|
|
56
|
+
const __filefullname=path.resolve(filename)
|
|
57
|
+
const {dir: __dirname, base: __filename}=path.parse(__filefullname)
|
|
58
|
+
const ctx0={__dirname, __filename, __filefullname}
|
|
59
|
+
for(let k in ctx0) {
|
|
60
|
+
ret+=`const ${k}=${JSON.stringify(ctx0[k])};`
|
|
61
|
+
}
|
|
62
|
+
for(let i=0; i<tokens.length; i++) {
|
|
63
|
+
if(tokens[i].tk===T_JS) {
|
|
64
|
+
ret+=tokens[i].tk_params+'\n'
|
|
65
|
+
}else if(tokens[i].tk===T_TEXT) {
|
|
66
|
+
ret+='\necho('+JSON.stringify(tokens[i].tk_params)+');\n'
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
return [ctx0, new vm.Script(ret+'; Symbol()[0]=_=>0', filename)]
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function compileSptcFile(filename, option) {
|
|
73
|
+
return compileFile(filename, {
|
|
74
|
+
compileFunc: content=>transformToAst(lexer(content), filename),
|
|
75
|
+
...(option || {}),
|
|
76
|
+
})
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
module.exports={
|
|
80
|
+
compileSptcFile,
|
|
81
|
+
}
|