ndnc 0.0.1__tar.gz
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.
- ndnc-0.0.1/LICENSE +21 -0
- ndnc-0.0.1/PKG-INFO +57 -0
- ndnc-0.0.1/README.md +36 -0
- ndnc-0.0.1/pyproject.toml +36 -0
- ndnc-0.0.1/setup.cfg +4 -0
- ndnc-0.0.1/src/ndnc/__init__.py +1 -0
- ndnc-0.0.1/src/ndnc/cli.py +32 -0
- ndnc-0.0.1/src/ndnc/interp/__init__.py +0 -0
- ndnc-0.0.1/src/ndnc/interp/evaluator.py +255 -0
- ndnc-0.0.1/src/ndnc/parser/__init__.py +1 -0
- ndnc-0.0.1/src/ndnc/parser/ast.py +46 -0
- ndnc-0.0.1/src/ndnc/parser/grammar.lark +26 -0
- ndnc-0.0.1/src/ndnc/parser/parser.py +78 -0
- ndnc-0.0.1/src/ndnc/remote_modify.py +222 -0
- ndnc-0.0.1/src/ndnc/server.py +40 -0
- ndnc-0.0.1/src/ndnc.egg-info/PKG-INFO +57 -0
- ndnc-0.0.1/src/ndnc.egg-info/SOURCES.txt +19 -0
- ndnc-0.0.1/src/ndnc.egg-info/dependency_links.txt +1 -0
- ndnc-0.0.1/src/ndnc.egg-info/entry_points.txt +2 -0
- ndnc-0.0.1/src/ndnc.egg-info/requires.txt +2 -0
- ndnc-0.0.1/src/ndnc.egg-info/top_level.txt +1 -0
ndnc-0.0.1/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 tryuuu
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
ndnc-0.0.1/PKG-INFO
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: ndnc
|
|
3
|
+
Version: 0.0.1
|
|
4
|
+
Summary: A DSL interpreter for transparent distributed execution over NDN
|
|
5
|
+
Author-email: tryuuu <ryu23210@gmail.com>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/tryuuu/ndn-compiler
|
|
8
|
+
Project-URL: Repository, https://github.com/tryuuu/ndn-compiler
|
|
9
|
+
Keywords: ndn,dsl,distributed,icn
|
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
14
|
+
Classifier: Topic :: Software Development :: Interpreters
|
|
15
|
+
Requires-Python: >=3.10
|
|
16
|
+
Description-Content-Type: text/markdown
|
|
17
|
+
License-File: LICENSE
|
|
18
|
+
Requires-Dist: lark==1.1.9
|
|
19
|
+
Requires-Dist: python-ndn
|
|
20
|
+
Dynamic: license-file
|
|
21
|
+
|
|
22
|
+
# Description
|
|
23
|
+
A minimal domain-specific language (DSL) interpreter for NDN-less syntax.
|
|
24
|
+
Currently supports several simple operations.
|
|
25
|
+
# Setup
|
|
26
|
+
## Start Environment (Docker)
|
|
27
|
+
Build and start NFD and Producer containers.
|
|
28
|
+
```bash
|
|
29
|
+
make all
|
|
30
|
+
```
|
|
31
|
+
## Run examples
|
|
32
|
+
Run the consumer in a container.
|
|
33
|
+
```bash
|
|
34
|
+
make run
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
ローカル関数(`modify`)の動作確認:
|
|
38
|
+
```bash
|
|
39
|
+
make run S=examples/hello.ndn
|
|
40
|
+
# 出力例: local data from function
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
リモート関数(`remote_modify`)の動作確認:
|
|
44
|
+
```bash
|
|
45
|
+
make run S=examples/remote.ndn
|
|
46
|
+
# 出力例: local data from remote_modify
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
`remote_modify` は NDN Interest `/remote_modify/<arg>` を発行し、`remote_modify` コンテナが処理を行う。`make all` 実行時に自動で起動する。
|
|
50
|
+
## Check Logs
|
|
51
|
+
```bash
|
|
52
|
+
make logs
|
|
53
|
+
```
|
|
54
|
+
## Stop Environment
|
|
55
|
+
```bash
|
|
56
|
+
make down
|
|
57
|
+
```
|
ndnc-0.0.1/README.md
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# Description
|
|
2
|
+
A minimal domain-specific language (DSL) interpreter for NDN-less syntax.
|
|
3
|
+
Currently supports several simple operations.
|
|
4
|
+
# Setup
|
|
5
|
+
## Start Environment (Docker)
|
|
6
|
+
Build and start NFD and Producer containers.
|
|
7
|
+
```bash
|
|
8
|
+
make all
|
|
9
|
+
```
|
|
10
|
+
## Run examples
|
|
11
|
+
Run the consumer in a container.
|
|
12
|
+
```bash
|
|
13
|
+
make run
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
ローカル関数(`modify`)の動作確認:
|
|
17
|
+
```bash
|
|
18
|
+
make run S=examples/hello.ndn
|
|
19
|
+
# 出力例: local data from function
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
リモート関数(`remote_modify`)の動作確認:
|
|
23
|
+
```bash
|
|
24
|
+
make run S=examples/remote.ndn
|
|
25
|
+
# 出力例: local data from remote_modify
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
`remote_modify` は NDN Interest `/remote_modify/<arg>` を発行し、`remote_modify` コンテナが処理を行う。`make all` 実行時に自動で起動する。
|
|
29
|
+
## Check Logs
|
|
30
|
+
```bash
|
|
31
|
+
make logs
|
|
32
|
+
```
|
|
33
|
+
## Stop Environment
|
|
34
|
+
```bash
|
|
35
|
+
make down
|
|
36
|
+
```
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=68", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
[project]
|
|
7
|
+
name = "ndnc"
|
|
8
|
+
version = "0.0.1"
|
|
9
|
+
description = "A DSL interpreter for transparent distributed execution over NDN"
|
|
10
|
+
authors = [{ name = "tryuuu", email = "ryu23210@gmail.com" }]
|
|
11
|
+
readme = "README.md"
|
|
12
|
+
license = "MIT"
|
|
13
|
+
license-files = ["LICENSE"]
|
|
14
|
+
requires-python = ">=3.10"
|
|
15
|
+
dependencies = [
|
|
16
|
+
"lark==1.1.9",
|
|
17
|
+
"python-ndn"
|
|
18
|
+
]
|
|
19
|
+
keywords = ["ndn", "dsl", "distributed", "icn"]
|
|
20
|
+
classifiers = [
|
|
21
|
+
"Programming Language :: Python :: 3",
|
|
22
|
+
"Programming Language :: Python :: 3.10",
|
|
23
|
+
"Programming Language :: Python :: 3.11",
|
|
24
|
+
"Programming Language :: Python :: 3.12",
|
|
25
|
+
"Topic :: Software Development :: Interpreters",
|
|
26
|
+
]
|
|
27
|
+
|
|
28
|
+
[project.urls]
|
|
29
|
+
Homepage = "https://github.com/tryuuu/ndn-compiler"
|
|
30
|
+
Repository = "https://github.com/tryuuu/ndn-compiler"
|
|
31
|
+
|
|
32
|
+
[project.scripts]
|
|
33
|
+
ndnc = "ndnc.cli:main"
|
|
34
|
+
|
|
35
|
+
[tool.setuptools.package-data]
|
|
36
|
+
"ndnc.parser" = ["*.lark"]
|
ndnc-0.0.1/setup.cfg
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__all__ = []
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
import argparse
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from .parser.parser import parse
|
|
5
|
+
from .interp.evaluator import Interpreter
|
|
6
|
+
from .server import Server
|
|
7
|
+
|
|
8
|
+
def main():
|
|
9
|
+
ap = argparse.ArgumentParser(prog="ndnc", description="NDN-less minimal DSL interpreter (print only)")
|
|
10
|
+
sub = ap.add_subparsers(dest="cmd", required=True)
|
|
11
|
+
|
|
12
|
+
ap_run = sub.add_parser("run", help="Interpret and run a .ndn file")
|
|
13
|
+
ap_run.add_argument("source", type=Path)
|
|
14
|
+
ap_run.add_argument("ndn_args", nargs="*", metavar="ARG",
|
|
15
|
+
help="NDN names passed as arg0, arg1, ... to the script")
|
|
16
|
+
|
|
17
|
+
ap_serve = sub.add_parser("serve", help="Start NDN server (producer)")
|
|
18
|
+
|
|
19
|
+
args = ap.parse_args()
|
|
20
|
+
|
|
21
|
+
if args.cmd == "run":
|
|
22
|
+
code = args.source.read_text(encoding="utf-8")
|
|
23
|
+
prog = parse(code)
|
|
24
|
+
# 位置引数を arg0, arg1, ... として Interpreter に渡す
|
|
25
|
+
ndn_args = {f"arg{i}": v for i, v in enumerate(args.ndn_args)}
|
|
26
|
+
Interpreter(args=ndn_args).run(prog)
|
|
27
|
+
elif args.cmd == "serve":
|
|
28
|
+
Server().run()
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
if __name__ == "__main__":
|
|
32
|
+
main()
|
|
File without changes
|
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
from typing import Any, Union, Optional
|
|
3
|
+
import asyncio
|
|
4
|
+
import contextlib
|
|
5
|
+
import io
|
|
6
|
+
import traceback
|
|
7
|
+
import sys
|
|
8
|
+
from ndn.app import NDNApp
|
|
9
|
+
from ndn.encoding import Name
|
|
10
|
+
from ndn.security import KeychainDigest
|
|
11
|
+
from ..parser.ast import (
|
|
12
|
+
Program, PrintStatement, Assignment, ExprStatement,
|
|
13
|
+
StringLiteral, NumberLiteral, Variable,
|
|
14
|
+
ExpressInterest, FunctionCall, Expr
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
# ローカルで処理できる関数名のセット
|
|
18
|
+
_LOCAL_FUNCTIONS = {"modify", "concat"}
|
|
19
|
+
|
|
20
|
+
class Interpreter:
|
|
21
|
+
def __init__(self, args: dict[str, str] | None = None):
|
|
22
|
+
self._env: dict[str, Any] = {}
|
|
23
|
+
self._env_origin: dict[str, str] = {} # interest で取得した変数の NDN 名を追跡
|
|
24
|
+
# 外部から渡された引数を env に事前登録(例: {"arg0": "/data/ryu-local/"})
|
|
25
|
+
if args:
|
|
26
|
+
self._env.update(args)
|
|
27
|
+
self.app: Optional[NDNApp] = None
|
|
28
|
+
self._local_data: dict[str, str] = {
|
|
29
|
+
'/data/ryu-local/': 'local data',
|
|
30
|
+
'/height/Mt.Fuji/': '3776m',
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
def run(self, program: Program):
|
|
34
|
+
has_interest = any(
|
|
35
|
+
(isinstance(st, ExprStatement) and self._has_interest(st.expr)) or
|
|
36
|
+
(isinstance(st, PrintStatement) and self._has_interest(st.expr)) or
|
|
37
|
+
(isinstance(st, Assignment) and self._has_interest(st.expr))
|
|
38
|
+
for st in program
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
if has_interest:
|
|
42
|
+
try:
|
|
43
|
+
self.app = NDNApp(keychain=KeychainDigest())
|
|
44
|
+
# ローカルデータを NDN プロデューサーとして登録する
|
|
45
|
+
# (リモート関数がこれらをフェッチできるようにするため)
|
|
46
|
+
self._register_local_data_routes()
|
|
47
|
+
|
|
48
|
+
async def after_start():
|
|
49
|
+
try:
|
|
50
|
+
await self._exec_block(program)
|
|
51
|
+
except Exception:
|
|
52
|
+
traceback.print_exc()
|
|
53
|
+
raise
|
|
54
|
+
finally:
|
|
55
|
+
self.app.shutdown()
|
|
56
|
+
|
|
57
|
+
self.app.run_forever(after_start=after_start())
|
|
58
|
+
|
|
59
|
+
except Exception as e:
|
|
60
|
+
print(f"DEBUG: NDN Connection/Execution Failed: {e}")
|
|
61
|
+
traceback.print_exc()
|
|
62
|
+
self.app = None
|
|
63
|
+
asyncio.run(self._exec_block(program))
|
|
64
|
+
else:
|
|
65
|
+
asyncio.run(self._exec_block(program))
|
|
66
|
+
|
|
67
|
+
def _has_interest(self, expr: Expr) -> bool:
|
|
68
|
+
if isinstance(expr, ExpressInterest):
|
|
69
|
+
if expr.name_is_var:
|
|
70
|
+
# 変数の値は実行時まで不明なのでネットワーク必要とみなす
|
|
71
|
+
return True
|
|
72
|
+
# _local_data にあればネットワーク不要
|
|
73
|
+
return expr.name not in self._local_data
|
|
74
|
+
if isinstance(expr, Variable):
|
|
75
|
+
return False
|
|
76
|
+
if isinstance(expr, FunctionCall):
|
|
77
|
+
# ローカルにない関数はリモート呼び出しになるため NDNApp が必要
|
|
78
|
+
return (expr.name not in _LOCAL_FUNCTIONS) or any(self._has_interest(a) for a in expr.args)
|
|
79
|
+
return False
|
|
80
|
+
|
|
81
|
+
async def _exec_block(self, node: Program):
|
|
82
|
+
for st in node:
|
|
83
|
+
if isinstance(st, PrintStatement):
|
|
84
|
+
await self._exec_print(st)
|
|
85
|
+
elif isinstance(st, Assignment):
|
|
86
|
+
await self._exec_assignment(st)
|
|
87
|
+
elif isinstance(st, ExprStatement):
|
|
88
|
+
await self._exec_expr_stmt(st)
|
|
89
|
+
else:
|
|
90
|
+
raise RuntimeError(f"Unsupported node: {st}")
|
|
91
|
+
|
|
92
|
+
async def _exec_print(self, node: PrintStatement):
|
|
93
|
+
value = await self._eval_expr(node.expr)
|
|
94
|
+
print(value)
|
|
95
|
+
|
|
96
|
+
async def _exec_assignment(self, node: Assignment):
|
|
97
|
+
value = await self._eval_expr(node.expr)
|
|
98
|
+
self._env[node.name] = value
|
|
99
|
+
# interest で取得した変数は NDN 名を記録しておく
|
|
100
|
+
if isinstance(node.expr, ExpressInterest):
|
|
101
|
+
if node.expr.name_is_var:
|
|
102
|
+
# 変数名から実際の NDN 名を解決して記録
|
|
103
|
+
ndn_name = self._env.get(node.expr.name, "")
|
|
104
|
+
self._env_origin[node.name] = str(ndn_name)
|
|
105
|
+
else:
|
|
106
|
+
self._env_origin[node.name] = node.expr.name
|
|
107
|
+
|
|
108
|
+
async def _exec_expr_stmt(self, node: ExprStatement):
|
|
109
|
+
value = await self._eval_expr(node.expr)
|
|
110
|
+
print(value)
|
|
111
|
+
|
|
112
|
+
async def _eval_expr(self, expr: Expr) -> Union[int, str]:
|
|
113
|
+
if isinstance(expr, StringLiteral):
|
|
114
|
+
return expr.value
|
|
115
|
+
|
|
116
|
+
if isinstance(expr, NumberLiteral):
|
|
117
|
+
return expr.value
|
|
118
|
+
|
|
119
|
+
if isinstance(expr, Variable):
|
|
120
|
+
if expr.name not in self._env:
|
|
121
|
+
raise RuntimeError(f"Variable '{expr.name}' is not defined")
|
|
122
|
+
return self._env[expr.name]
|
|
123
|
+
|
|
124
|
+
if isinstance(expr, ExpressInterest):
|
|
125
|
+
# name_is_var のとき、変数から実際の NDN 名を解決する
|
|
126
|
+
if expr.name_is_var:
|
|
127
|
+
if expr.name not in self._env:
|
|
128
|
+
raise RuntimeError(f"Variable '{expr.name}' is not defined (used in interest)")
|
|
129
|
+
ndn_name = str(self._env[expr.name])
|
|
130
|
+
else:
|
|
131
|
+
ndn_name = expr.name
|
|
132
|
+
|
|
133
|
+
if not ndn_name.endswith('/'):
|
|
134
|
+
print(f"Error: Interest name must end with a trailing slash. Got: {ndn_name}", file=sys.stderr)
|
|
135
|
+
print(f"Expected: {ndn_name}/", file=sys.stderr)
|
|
136
|
+
sys.exit(1)
|
|
137
|
+
|
|
138
|
+
if ndn_name in self._local_data:
|
|
139
|
+
local_value = self._local_data[ndn_name]
|
|
140
|
+
try:
|
|
141
|
+
return int(local_value)
|
|
142
|
+
except ValueError:
|
|
143
|
+
return local_value
|
|
144
|
+
|
|
145
|
+
if self.app is None:
|
|
146
|
+
return f"mock_{ndn_name.replace('/', '_')}"
|
|
147
|
+
|
|
148
|
+
try:
|
|
149
|
+
_, _, content = await self.app.express_interest(
|
|
150
|
+
ndn_name,
|
|
151
|
+
must_be_fresh=True,
|
|
152
|
+
can_be_prefix=True,
|
|
153
|
+
lifetime=6000
|
|
154
|
+
)
|
|
155
|
+
if content is None:
|
|
156
|
+
return ""
|
|
157
|
+
text = bytes(content).decode('utf-8').strip()
|
|
158
|
+
try:
|
|
159
|
+
return int(text)
|
|
160
|
+
except ValueError:
|
|
161
|
+
return text
|
|
162
|
+
except Exception as e:
|
|
163
|
+
print(f"Error expressing interest for {expr.name}: {e}")
|
|
164
|
+
raise e
|
|
165
|
+
|
|
166
|
+
if isinstance(expr, FunctionCall):
|
|
167
|
+
if expr.name in _LOCAL_FUNCTIONS:
|
|
168
|
+
arg_values = [await self._eval_expr(a) for a in expr.args]
|
|
169
|
+
if expr.name == "m_to_feet":
|
|
170
|
+
meters_str = str(arg_values[0]).rstrip('m')
|
|
171
|
+
feet = round(float(meters_str) * 3.28084)
|
|
172
|
+
return f"{feet}ft"
|
|
173
|
+
if expr.name == "concat":
|
|
174
|
+
return "".join(str(v) for v in arg_values)
|
|
175
|
+
return str(arg_values[0]) + " from function"
|
|
176
|
+
elif self.app is not None:
|
|
177
|
+
# リモート関数: 引数を NDN 名として渡す(ネストした関数呼び出しも再帰的に解決)
|
|
178
|
+
ndn_names = [self._to_ndn_name(a) for a in expr.args]
|
|
179
|
+
return await self._call_remote_function(expr.name, ndn_names)
|
|
180
|
+
else:
|
|
181
|
+
raise RuntimeError(f"Unknown function: {expr.name}")
|
|
182
|
+
|
|
183
|
+
raise RuntimeError(f"Unsupported expr: {expr}")
|
|
184
|
+
|
|
185
|
+
def _register_local_data_routes(self):
|
|
186
|
+
"""ローカルデータを NDN プロデューサーとして登録する。
|
|
187
|
+
リモート関数がフェッチできるよう、Interest に応答できるようにする。"""
|
|
188
|
+
for ndn_name, value in self._local_data.items():
|
|
189
|
+
prefix = ndn_name.rstrip('/')
|
|
190
|
+
val_bytes = str(value).encode()
|
|
191
|
+
|
|
192
|
+
def make_handler(content):
|
|
193
|
+
def handler(name, param, app_param):
|
|
194
|
+
self.app.put_data(name, content=content, freshness_period=10000)
|
|
195
|
+
return handler
|
|
196
|
+
|
|
197
|
+
self.app.route(prefix)(make_handler(val_bytes))
|
|
198
|
+
|
|
199
|
+
def _to_ndn_name(self, expr: Expr) -> str:
|
|
200
|
+
"""リモート関数の引数として使う NDN 名を決定する。
|
|
201
|
+
- ExpressInterest → そのまま NDN 名を返す
|
|
202
|
+
- Variable → interest 由来なら記録済みの NDN 名、そうでなければ値を NDN 名として扱う
|
|
203
|
+
- StringLiteral → 先頭 '/' を補完して NDN 名とする"""
|
|
204
|
+
if isinstance(expr, ExpressInterest):
|
|
205
|
+
if expr.name_is_var:
|
|
206
|
+
if expr.name in self._env_origin:
|
|
207
|
+
return self._env_origin[expr.name]
|
|
208
|
+
val = self._env.get(expr.name, "")
|
|
209
|
+
return str(val) if str(val).startswith('/') else '/' + str(val)
|
|
210
|
+
return expr.name
|
|
211
|
+
if isinstance(expr, Variable):
|
|
212
|
+
if expr.name in self._env_origin:
|
|
213
|
+
return self._env_origin[expr.name]
|
|
214
|
+
val = self._env.get(expr.name, "")
|
|
215
|
+
if isinstance(val, str):
|
|
216
|
+
return val if val.startswith('/') else '/' + val
|
|
217
|
+
return str(val)
|
|
218
|
+
if isinstance(expr, StringLiteral):
|
|
219
|
+
val = expr.value
|
|
220
|
+
return val if val.startswith('/') else '/' + val
|
|
221
|
+
if isinstance(expr, FunctionCall):
|
|
222
|
+
if expr.name not in _LOCAL_FUNCTIONS:
|
|
223
|
+
ndn_names = [self._to_ndn_name(a) for a in expr.args]
|
|
224
|
+
args_str = ", ".join(ndn_names)
|
|
225
|
+
return "/" + expr.name + "/(" + args_str + ")"
|
|
226
|
+
return str(expr)
|
|
227
|
+
|
|
228
|
+
async def _call_remote_function(self, func_name: str, ndn_names: list[str]) -> str:
|
|
229
|
+
# Sidecar に倣い、括弧記法で Interest 名を構築する
|
|
230
|
+
# 例: /temperature_average/(/data/tokyo, /data/paris)
|
|
231
|
+
args_str = ", ".join(ndn_names)
|
|
232
|
+
interest_name = "/" + func_name + "/(" + args_str + ")"
|
|
233
|
+
try:
|
|
234
|
+
_, _, content = await self.app.express_interest(
|
|
235
|
+
interest_name,
|
|
236
|
+
must_be_fresh=True,
|
|
237
|
+
can_be_prefix=False,
|
|
238
|
+
lifetime=20000 # リモート関数が引数をフェッチする時間を考慮して長めに設定
|
|
239
|
+
)
|
|
240
|
+
if content is None:
|
|
241
|
+
return ""
|
|
242
|
+
return bytes(content).decode('utf-8').strip()
|
|
243
|
+
except Exception as e:
|
|
244
|
+
print(f"Error calling remote function '{func_name}': {e}")
|
|
245
|
+
raise
|
|
246
|
+
|
|
247
|
+
async def exec_in_context(self, program: Program, app: NDNApp) -> str:
|
|
248
|
+
"""既存の NDNApp のコンテキスト内で .ndn プログラムを実行し、出力を文字列で返す。
|
|
249
|
+
seed サーバーなど、すでにイベントループが動いている環境から呼び出す用途向け。
|
|
250
|
+
通常の run() と異なり、新たなイベントループや NDNApp を起動しない。"""
|
|
251
|
+
self.app = app
|
|
252
|
+
buffer = io.StringIO()
|
|
253
|
+
with contextlib.redirect_stdout(buffer):
|
|
254
|
+
await self._exec_block(program)
|
|
255
|
+
return buffer.getvalue().strip()
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__all__ = []
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from typing import List, Union
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
@dataclass
|
|
8
|
+
class PrintStatement:
|
|
9
|
+
expr: "Expr"
|
|
10
|
+
|
|
11
|
+
@dataclass
|
|
12
|
+
class Assignment:
|
|
13
|
+
name: str
|
|
14
|
+
expr: "Expr"
|
|
15
|
+
|
|
16
|
+
@dataclass
|
|
17
|
+
class StringLiteral:
|
|
18
|
+
value: str
|
|
19
|
+
|
|
20
|
+
@dataclass
|
|
21
|
+
class NumberLiteral:
|
|
22
|
+
value: int
|
|
23
|
+
|
|
24
|
+
@dataclass
|
|
25
|
+
class Variable:
|
|
26
|
+
name: str
|
|
27
|
+
|
|
28
|
+
@dataclass
|
|
29
|
+
class ExpressInterest:
|
|
30
|
+
name: str
|
|
31
|
+
name_is_var: bool = False # True のとき name は変数名(実行時に env から解決する)
|
|
32
|
+
|
|
33
|
+
@dataclass
|
|
34
|
+
class FunctionCall:
|
|
35
|
+
name: str
|
|
36
|
+
args: List["Expr"]
|
|
37
|
+
|
|
38
|
+
Expr = Union[StringLiteral, NumberLiteral, Variable, ExpressInterest, FunctionCall]
|
|
39
|
+
|
|
40
|
+
@dataclass
|
|
41
|
+
class ExprStatement:
|
|
42
|
+
expr: Expr
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
Statement = Union[PrintStatement, Assignment, ExprStatement]
|
|
46
|
+
Program = List[Statement]
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
// Simple grammar: only print statements of string literals
|
|
2
|
+
|
|
3
|
+
start: statement+
|
|
4
|
+
|
|
5
|
+
// 定義できる文法
|
|
6
|
+
?statement: LET IDENTIFIER "=" expr -> assignment_stmt
|
|
7
|
+
| PRINT expr -> print_stmt
|
|
8
|
+
| expr -> expr_stmt
|
|
9
|
+
|
|
10
|
+
// exprの種類を定義
|
|
11
|
+
?expr: STRING -> string_literal
|
|
12
|
+
| NUMBER -> number_literal
|
|
13
|
+
| IDENTIFIER "(" expr ("," expr)* ")" -> call_expr
|
|
14
|
+
| IDENTIFIER -> variable
|
|
15
|
+
| INTEREST STRING -> interest_expr
|
|
16
|
+
| INTEREST IDENTIFIER -> interest_var_expr
|
|
17
|
+
|
|
18
|
+
LET: "let"
|
|
19
|
+
PRINT: "print"
|
|
20
|
+
INTEREST: "interest"
|
|
21
|
+
IDENTIFIER: /[a-zA-Z_][a-zA-Z0-9_]*/
|
|
22
|
+
|
|
23
|
+
%import common.ESCAPED_STRING -> STRING
|
|
24
|
+
%import common.SIGNED_NUMBER -> NUMBER
|
|
25
|
+
%import common.WS
|
|
26
|
+
%ignore WS
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from typing import List
|
|
5
|
+
|
|
6
|
+
from lark import Lark, Transformer, v_args
|
|
7
|
+
|
|
8
|
+
from .ast import (
|
|
9
|
+
PrintStatement, Assignment, ExprStatement,
|
|
10
|
+
StringLiteral, NumberLiteral, Variable,
|
|
11
|
+
ExpressInterest, FunctionCall, Program, Expr
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def _load_grammar() -> str:
|
|
16
|
+
grammar_path = Path(__file__).with_name("grammar.lark")
|
|
17
|
+
return grammar_path.read_text(encoding="utf-8")
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
_PARSER = Lark(_load_grammar(), start="start", parser="lalr")
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class _BuildAST(Transformer):
|
|
24
|
+
@v_args(inline=True)
|
|
25
|
+
def assignment_stmt(self, let_token, identifier_token, expr: Expr): # type: ignore[override]
|
|
26
|
+
name = str(identifier_token)
|
|
27
|
+
return Assignment(name=name, expr=expr)
|
|
28
|
+
|
|
29
|
+
@v_args(inline=True)
|
|
30
|
+
def print_stmt(self, print_token, expr: Expr): # type: ignore[override]
|
|
31
|
+
return PrintStatement(expr=expr)
|
|
32
|
+
|
|
33
|
+
@v_args(inline=True)
|
|
34
|
+
def expr_stmt(self, expr: Expr): # type: ignore[override]
|
|
35
|
+
return ExprStatement(expr=expr)
|
|
36
|
+
|
|
37
|
+
@v_args(inline=True)
|
|
38
|
+
def string_literal(self, string_token): # type: ignore[override]
|
|
39
|
+
text = str(string_token)
|
|
40
|
+
if (text.startswith('"') and text.endswith('"')) or (text.startswith("'") and text.endswith("'")):
|
|
41
|
+
text = text[1:-1]
|
|
42
|
+
return StringLiteral(value=text)
|
|
43
|
+
|
|
44
|
+
@v_args(inline=True)
|
|
45
|
+
def number_literal(self, num_token): # type: ignore[override]
|
|
46
|
+
return NumberLiteral(value=int(str(num_token)))
|
|
47
|
+
|
|
48
|
+
@v_args(inline=True)
|
|
49
|
+
def variable(self, identifier_token): # type: ignore[override]
|
|
50
|
+
name = str(identifier_token)
|
|
51
|
+
return Variable(name=name)
|
|
52
|
+
|
|
53
|
+
@v_args(inline=True)
|
|
54
|
+
def interest_expr(self, interest_token, string_token): # type: ignore[override]
|
|
55
|
+
text = str(string_token)
|
|
56
|
+
if (text.startswith('"') and text.endswith('"')) or (text.startswith("'") and text.endswith("'")):
|
|
57
|
+
text = text[1:-1]
|
|
58
|
+
return ExpressInterest(name=text)
|
|
59
|
+
|
|
60
|
+
@v_args(inline=True)
|
|
61
|
+
def interest_var_expr(self, interest_token, identifier_token): # type: ignore[override]
|
|
62
|
+
return ExpressInterest(name=str(identifier_token), name_is_var=True)
|
|
63
|
+
|
|
64
|
+
def call_expr(self, items): # type: ignore[override]
|
|
65
|
+
name = str(items[0])
|
|
66
|
+
args = list(items[1:])
|
|
67
|
+
return FunctionCall(name=name, args=args)
|
|
68
|
+
|
|
69
|
+
def start(self, stmts): # type: ignore[override]
|
|
70
|
+
if isinstance(stmts, list):
|
|
71
|
+
return stmts
|
|
72
|
+
else:
|
|
73
|
+
return [stmts]
|
|
74
|
+
|
|
75
|
+
def parse(source: str) -> Program:
|
|
76
|
+
tree = _PARSER.parse(source)
|
|
77
|
+
program: Program = _BuildAST().transform(tree)
|
|
78
|
+
return program
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import urllib.parse
|
|
3
|
+
from typing import Optional
|
|
4
|
+
from ndn.app import NDNApp
|
|
5
|
+
from ndn.encoding import Name, InterestParam, BinaryStr, FormalName
|
|
6
|
+
from ndn.security import KeychainDigest
|
|
7
|
+
from ndn.types import InterestNack, InterestTimeout
|
|
8
|
+
|
|
9
|
+
import logging
|
|
10
|
+
logging.basicConfig(format='[{asctime}]{levelname}:{message}',
|
|
11
|
+
datefmt='%Y-%m-%d %H:%M:%S',
|
|
12
|
+
level=logging.INFO,
|
|
13
|
+
style='{')
|
|
14
|
+
|
|
15
|
+
_TEMPERATURE_DATA: dict[str, float] = {
|
|
16
|
+
'/data/tokyo': 22.5,
|
|
17
|
+
'/data/paris': 18.0,
|
|
18
|
+
'/data/newyork': 15.3,
|
|
19
|
+
'/data/london': 12.8,
|
|
20
|
+
'/data/sydney': 26.1,
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
app = NDNApp(keychain=KeychainDigest())
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def decode_and_remove_metadata(name: FormalName) -> str:
|
|
27
|
+
"""NDN FormalName をデコードし、/t= などのメタデータを除去して返す。"""
|
|
28
|
+
decoded = Name.to_str(name)
|
|
29
|
+
decoded = urllib.parse.unquote(decoded)
|
|
30
|
+
# ')' の直後に '/' が続く場合はそこで打ち切る
|
|
31
|
+
end = decoded.rfind(')')
|
|
32
|
+
if end != -1 and len(decoded) > end + 1 and decoded[end + 1] == '/':
|
|
33
|
+
decoded = decoded[:end + 1]
|
|
34
|
+
# /t= メタデータを除去
|
|
35
|
+
t_idx = decoded.rfind('/t=')
|
|
36
|
+
if t_idx != -1:
|
|
37
|
+
decoded = decoded[:t_idx]
|
|
38
|
+
return decoded
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def is_function_request(name: FormalName) -> bool:
|
|
42
|
+
"""/( が含まれていれば関数リクエストと判定する。"""
|
|
43
|
+
return "/(" in decode_and_remove_metadata(name)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def extract_first_level_args(name: FormalName) -> list[str]:
|
|
47
|
+
"""関数 Interest から第一階層の引数(NDN 名)リストを取り出す。
|
|
48
|
+
ネストした括弧にも対応。例: /f/(/a, /g/(/b)) → ['/a', '/g/(/b)']"""
|
|
49
|
+
decoded = decode_and_remove_metadata(name)
|
|
50
|
+
if '(' not in decoded:
|
|
51
|
+
return [decoded.strip()]
|
|
52
|
+
|
|
53
|
+
start_of_args = decoded.find('/(') + 2 # '(' の直後
|
|
54
|
+
args_str = decoded[start_of_args:-1] # 末尾の ')' を除く
|
|
55
|
+
|
|
56
|
+
args: list[str] = []
|
|
57
|
+
start = 0
|
|
58
|
+
depth = 0
|
|
59
|
+
for i, ch in enumerate(args_str):
|
|
60
|
+
if ch == '(':
|
|
61
|
+
depth += 1
|
|
62
|
+
elif ch == ')':
|
|
63
|
+
depth -= 1
|
|
64
|
+
elif ch == ',' and depth == 0:
|
|
65
|
+
args.append(args_str[start:i].strip())
|
|
66
|
+
start = i + 1
|
|
67
|
+
args.append(args_str[start:].strip())
|
|
68
|
+
return args
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
async def _fetch_arg(ndn_name: str) -> Optional[bytes]:
|
|
72
|
+
"""引数の NDN 名を解決する。
|
|
73
|
+
ローカルデータストアを先に確認し、なければ consumer_app で Interest を発行する。
|
|
74
|
+
引数がネストした関数呼び出しの場合も再帰的に解決される。"""
|
|
75
|
+
key = ndn_name.rstrip('/')
|
|
76
|
+
if key in _TEMPERATURE_DATA:
|
|
77
|
+
logging.info(f"[local] {key} = {_TEMPERATURE_DATA[key]}")
|
|
78
|
+
return str(_TEMPERATURE_DATA[key]).encode()
|
|
79
|
+
|
|
80
|
+
# NDN ネットワーク経由で取得(ネストした関数呼び出しを含む)
|
|
81
|
+
logging.info(f"[fetch] {ndn_name}")
|
|
82
|
+
for attempt in range(3):
|
|
83
|
+
try:
|
|
84
|
+
_, _, content = await app.express_interest(
|
|
85
|
+
ndn_name, must_be_fresh=True, can_be_prefix=False, lifetime=10000
|
|
86
|
+
)
|
|
87
|
+
if content:
|
|
88
|
+
logging.info(f"[fetch] OK {ndn_name} (attempt {attempt + 1})")
|
|
89
|
+
return bytes(content)
|
|
90
|
+
except (InterestNack, InterestTimeout) as e:
|
|
91
|
+
logging.warning(f"[fetch] {ndn_name} attempt {attempt + 1} failed: {e}")
|
|
92
|
+
await asyncio.sleep(0.3)
|
|
93
|
+
logging.error(f"[fetch] GIVE-UP {ndn_name}")
|
|
94
|
+
return None
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
@app.route('/data')
|
|
98
|
+
def on_data(name: FormalName, param: InterestParam, _app_param: Optional[BinaryStr]):
|
|
99
|
+
"""データリクエストに応答する(/data/* プレフィックス)。"""
|
|
100
|
+
name_str = Name.to_str(name)
|
|
101
|
+
t_idx = name_str.rfind('/t=')
|
|
102
|
+
if t_idx != -1:
|
|
103
|
+
name_str = name_str[:t_idx]
|
|
104
|
+
key = name_str.rstrip('/')
|
|
105
|
+
if key in _TEMPERATURE_DATA:
|
|
106
|
+
logging.info(f"Serving data: {key}")
|
|
107
|
+
app.put_data(name, content=str(_TEMPERATURE_DATA[key]).encode(), freshness_period=10000)
|
|
108
|
+
else:
|
|
109
|
+
logging.warning(f"Unknown data key: {key}")
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
@app.route('/remote_modify')
|
|
113
|
+
def on_modify(name: FormalName, param: InterestParam, _app_param: Optional[BinaryStr]):
|
|
114
|
+
async def handler():
|
|
115
|
+
logging.info(f"Interest: {Name.to_str(name)}")
|
|
116
|
+
if not is_function_request(name):
|
|
117
|
+
logging.warning("Not a function request")
|
|
118
|
+
return
|
|
119
|
+
|
|
120
|
+
args = extract_first_level_args(name)
|
|
121
|
+
logging.info(f"Args: {args}")
|
|
122
|
+
|
|
123
|
+
contents = await asyncio.gather(*[_fetch_arg(a) for a in args])
|
|
124
|
+
if any(c is None for c in contents):
|
|
125
|
+
app.put_data(name, content=b"error: failed to fetch argument", freshness_period=10000)
|
|
126
|
+
return
|
|
127
|
+
|
|
128
|
+
result = f"{contents[0].decode()} from remote_modify"
|
|
129
|
+
logging.info(f"Result: {result!r}")
|
|
130
|
+
app.put_data(name, content=result.encode(), freshness_period=10000)
|
|
131
|
+
|
|
132
|
+
asyncio.create_task(handler())
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
@app.route('/temperature_average')
|
|
136
|
+
def on_temperature_average(name: FormalName, param: InterestParam, _app_param: Optional[BinaryStr]):
|
|
137
|
+
async def handler():
|
|
138
|
+
logging.info(f"Interest: {Name.to_str(name)}")
|
|
139
|
+
if not is_function_request(name):
|
|
140
|
+
logging.warning("Not a function request")
|
|
141
|
+
return
|
|
142
|
+
|
|
143
|
+
args = extract_first_level_args(name)
|
|
144
|
+
logging.info(f"Args: {args}")
|
|
145
|
+
|
|
146
|
+
contents = await asyncio.gather(*[_fetch_arg(a) for a in args])
|
|
147
|
+
if any(c is None for c in contents):
|
|
148
|
+
app.put_data(name, content=b"error: failed to fetch argument(s)", freshness_period=10000)
|
|
149
|
+
return
|
|
150
|
+
|
|
151
|
+
try:
|
|
152
|
+
temps = [float(c.decode()) for c in contents]
|
|
153
|
+
average = sum(temps) / len(temps)
|
|
154
|
+
result = f"{average:.1f}"
|
|
155
|
+
logging.info(f"temperature_average{tuple(args)} = {result}")
|
|
156
|
+
app.put_data(name, content=result.encode(), freshness_period=10000)
|
|
157
|
+
except ValueError as e:
|
|
158
|
+
app.put_data(name, content=f"error: {e}".encode(), freshness_period=10000)
|
|
159
|
+
|
|
160
|
+
asyncio.create_task(handler())
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
@app.route('/format_temp')
|
|
164
|
+
def on_format_temp(name: FormalName, param: InterestParam, _app_param: Optional[BinaryStr]):
|
|
165
|
+
async def handler():
|
|
166
|
+
logging.info(f"Interest: {Name.to_str(name)}")
|
|
167
|
+
if not is_function_request(name):
|
|
168
|
+
logging.warning("Not a function request")
|
|
169
|
+
return
|
|
170
|
+
|
|
171
|
+
args = extract_first_level_args(name)
|
|
172
|
+
logging.info(f"Args: {args}")
|
|
173
|
+
|
|
174
|
+
contents = await asyncio.gather(*[_fetch_arg(a) for a in args])
|
|
175
|
+
if any(c is None for c in contents):
|
|
176
|
+
app.put_data(name, content=b"error: failed to fetch argument", freshness_period=10000)
|
|
177
|
+
return
|
|
178
|
+
|
|
179
|
+
result = f"{contents[0].decode()}°C"
|
|
180
|
+
logging.info(f"Result: {result!r}")
|
|
181
|
+
app.put_data(name, content=result.encode(), freshness_period=10000)
|
|
182
|
+
|
|
183
|
+
asyncio.create_task(handler())
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
@app.route('/m_to_feet')
|
|
187
|
+
def on_m_to_feet(name: FormalName, param: InterestParam, _app_param: Optional[BinaryStr]):
|
|
188
|
+
async def handler():
|
|
189
|
+
logging.info(f"Interest: {Name.to_str(name)}")
|
|
190
|
+
if not is_function_request(name):
|
|
191
|
+
logging.warning("Not a function request")
|
|
192
|
+
return
|
|
193
|
+
|
|
194
|
+
args = extract_first_level_args(name)
|
|
195
|
+
logging.info(f"Args: {args}")
|
|
196
|
+
|
|
197
|
+
contents = await asyncio.gather(*[_fetch_arg(a) for a in args])
|
|
198
|
+
if any(c is None for c in contents):
|
|
199
|
+
app.put_data(name, content=b"error: failed to fetch argument", freshness_period=10000)
|
|
200
|
+
return
|
|
201
|
+
|
|
202
|
+
try:
|
|
203
|
+
meters_str = contents[0].decode().rstrip('m')
|
|
204
|
+
feet = round(float(meters_str) * 3.28084)
|
|
205
|
+
result = f"{feet}ft"
|
|
206
|
+
logging.info(f"Result: {result!r}")
|
|
207
|
+
app.put_data(name, content=result.encode(), freshness_period=10000)
|
|
208
|
+
except ValueError as e:
|
|
209
|
+
app.put_data(name, content=f"error: {e}".encode(), freshness_period=10000)
|
|
210
|
+
|
|
211
|
+
asyncio.create_task(handler())
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
if __name__ == '__main__':
|
|
215
|
+
print("Starting remote function node")
|
|
216
|
+
print(f" /remote_modify : /remote_modify/(<arg>)")
|
|
217
|
+
print(f" /temperature_average : /temperature_average/(<name1>, <name2>, ...)")
|
|
218
|
+
print(f" /format_temp : /format_temp/(<temp_value_or_func>)")
|
|
219
|
+
print(f" /m_to_feet : /m_to_feet/(<meters_value>)")
|
|
220
|
+
print(f" /data/* : temperature data")
|
|
221
|
+
print(f" Available data: {list(_TEMPERATURE_DATA.keys())}")
|
|
222
|
+
app.run_forever()
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import sys
|
|
4
|
+
|
|
5
|
+
from ndn.app import NDNApp
|
|
6
|
+
from ndn.encoding import Name
|
|
7
|
+
from ndn.security import KeychainDigest
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class Server:
|
|
11
|
+
def __init__(self):
|
|
12
|
+
try:
|
|
13
|
+
self.app = NDNApp(keychain=KeychainDigest())
|
|
14
|
+
except Exception as e:
|
|
15
|
+
print(f"Error: Failed to initialize NDNApp: {e}", file=sys.stderr)
|
|
16
|
+
sys.exit(1)
|
|
17
|
+
|
|
18
|
+
def run(self):
|
|
19
|
+
@self.app.route('/data/ryu')
|
|
20
|
+
def on_data_ryu(name, param, _app_param):
|
|
21
|
+
print(f"Received Interest: {Name.to_str(name)}")
|
|
22
|
+
self.app.put_data(name, content=b'success', freshness_period=10000)
|
|
23
|
+
print(f"Sent Data: {Name.to_str(name)} -> success")
|
|
24
|
+
print("Server started. Listening for Interests on /data/ryu...")
|
|
25
|
+
|
|
26
|
+
@self.app.route('/data/nakazatolab')
|
|
27
|
+
def on_data_nakazatolab(name, param, _app_param):
|
|
28
|
+
print(f"Received Interest: {Name.to_str(name)}")
|
|
29
|
+
self.app.put_data(name, content=b'NDN research', freshness_period=10000)
|
|
30
|
+
print(f"Sent Data: {Name.to_str(name)} -> NDN research")
|
|
31
|
+
print("Server started. Listening for Interests on /data/nakazatolab...")
|
|
32
|
+
|
|
33
|
+
@self.app.route('/height/Mt.Fuji')
|
|
34
|
+
def on_height_mt_fuji(name, param, _app_param):
|
|
35
|
+
print(f"Received Interest: {Name.to_str(name)}")
|
|
36
|
+
self.app.put_data(name, content=b'3776m', freshness_period=10000)
|
|
37
|
+
print(f"Sent Data: {Name.to_str(name)} -> 3776m")
|
|
38
|
+
print("Server started. Listening for Interests on /height/Mt.Fuji...")
|
|
39
|
+
|
|
40
|
+
self.app.run_forever()
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: ndnc
|
|
3
|
+
Version: 0.0.1
|
|
4
|
+
Summary: A DSL interpreter for transparent distributed execution over NDN
|
|
5
|
+
Author-email: tryuuu <ryu23210@gmail.com>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/tryuuu/ndn-compiler
|
|
8
|
+
Project-URL: Repository, https://github.com/tryuuu/ndn-compiler
|
|
9
|
+
Keywords: ndn,dsl,distributed,icn
|
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
14
|
+
Classifier: Topic :: Software Development :: Interpreters
|
|
15
|
+
Requires-Python: >=3.10
|
|
16
|
+
Description-Content-Type: text/markdown
|
|
17
|
+
License-File: LICENSE
|
|
18
|
+
Requires-Dist: lark==1.1.9
|
|
19
|
+
Requires-Dist: python-ndn
|
|
20
|
+
Dynamic: license-file
|
|
21
|
+
|
|
22
|
+
# Description
|
|
23
|
+
A minimal domain-specific language (DSL) interpreter for NDN-less syntax.
|
|
24
|
+
Currently supports several simple operations.
|
|
25
|
+
# Setup
|
|
26
|
+
## Start Environment (Docker)
|
|
27
|
+
Build and start NFD and Producer containers.
|
|
28
|
+
```bash
|
|
29
|
+
make all
|
|
30
|
+
```
|
|
31
|
+
## Run examples
|
|
32
|
+
Run the consumer in a container.
|
|
33
|
+
```bash
|
|
34
|
+
make run
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
ローカル関数(`modify`)の動作確認:
|
|
38
|
+
```bash
|
|
39
|
+
make run S=examples/hello.ndn
|
|
40
|
+
# 出力例: local data from function
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
リモート関数(`remote_modify`)の動作確認:
|
|
44
|
+
```bash
|
|
45
|
+
make run S=examples/remote.ndn
|
|
46
|
+
# 出力例: local data from remote_modify
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
`remote_modify` は NDN Interest `/remote_modify/<arg>` を発行し、`remote_modify` コンテナが処理を行う。`make all` 実行時に自動で起動する。
|
|
50
|
+
## Check Logs
|
|
51
|
+
```bash
|
|
52
|
+
make logs
|
|
53
|
+
```
|
|
54
|
+
## Stop Environment
|
|
55
|
+
```bash
|
|
56
|
+
make down
|
|
57
|
+
```
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
LICENSE
|
|
2
|
+
README.md
|
|
3
|
+
pyproject.toml
|
|
4
|
+
src/ndnc/__init__.py
|
|
5
|
+
src/ndnc/cli.py
|
|
6
|
+
src/ndnc/remote_modify.py
|
|
7
|
+
src/ndnc/server.py
|
|
8
|
+
src/ndnc.egg-info/PKG-INFO
|
|
9
|
+
src/ndnc.egg-info/SOURCES.txt
|
|
10
|
+
src/ndnc.egg-info/dependency_links.txt
|
|
11
|
+
src/ndnc.egg-info/entry_points.txt
|
|
12
|
+
src/ndnc.egg-info/requires.txt
|
|
13
|
+
src/ndnc.egg-info/top_level.txt
|
|
14
|
+
src/ndnc/interp/__init__.py
|
|
15
|
+
src/ndnc/interp/evaluator.py
|
|
16
|
+
src/ndnc/parser/__init__.py
|
|
17
|
+
src/ndnc/parser/ast.py
|
|
18
|
+
src/ndnc/parser/grammar.lark
|
|
19
|
+
src/ndnc/parser/parser.py
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
ndnc
|