diff --git a/.gitignore b/.gitignore index b7faf40..b376bff 100644 --- a/.gitignore +++ b/.gitignore @@ -1,16 +1,56 @@ -# Byte-compiled / optimized / DLL files +# 忽略编译产物 +build/ +dist/ __pycache__/ *.py[codz] *$py.class -# C extensions +# 忽略C扩展 *.so -# Distribution / packaging +# 忽略文档构建产物 +docs/site/ +docs/_build/ +/site + +# 忽略示例文件 +examples/ + +# 忽略扩展文件 +includes/ + +# 忽略测试文件 +test_*.oas + +# 忽略打包文件 +*.osp + +# 忽略临时文件 +*.tmp +*.temp + +# 忽略IDE文件 +.vscode/ +.idea/ +*.swp +*.swo +*~ + +# 忽略操作系统文件 +Thumbs.db +.DS_Store + +# 忽略日志文件 +*.log + +# 忽略配置文件 +*.ini +*.cfg +*.conf + +# 忽略安装相关文件 .Python -build/ develop-eggs/ -dist/ downloads/ eggs/ .eggs/ @@ -26,17 +66,15 @@ share/python-wheels/ *.egg MANIFEST -# PyInstaller -# Usually these files are written by a python script from a template -# before PyInstaller builds the exe, so as to inject date/other infos into it. +# 忽略PyInstaller文件 *.manifest *.spec -# Installer logs +# 忽略安装日志 pip-log.txt pip-delete-this-directory.txt -# Unit test / coverage reports +# 忽略测试覆盖率报告 htmlcov/ .tox/ .nox/ @@ -51,90 +89,34 @@ coverage.xml .pytest_cache/ cover/ -# Translations +# 忽略翻译文件 *.mo *.pot -# Django stuff: -*.log +# 忽略Django相关文件 local_settings.py db.sqlite3 db.sqlite3-journal -# Flask stuff: +# 忽略Flask相关文件 instance/ .webassets-cache -# Scrapy stuff: +# 忽略Scrapy相关文件 .scrapy -# Sphinx documentation -docs/_build/ - -# PyBuilder +# 忽略PyBuilder文件 .pybuilder/ target/ -# Jupyter Notebook +# 忽略Jupyter Notebook文件 .ipynb_checkpoints -# IPython +# 忽略IPython文件 profile_default/ ipython_config.py -# pyenv -# For a library or package, you might want to ignore these files since the code is -# intended to run in multiple environments; otherwise, check them in: -# .python-version - -# pipenv -# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. -# However, in case of collaboration, if having platform-specific dependencies or dependencies -# having no cross-platform support, pipenv may install dependencies that don't work, or not -# install all needed dependencies. -#Pipfile.lock - -# UV -# Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control. -# This is especially recommended for binary packages to ensure reproducibility, and is more -# commonly ignored for libraries. -#uv.lock - -# poetry -# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. -# This is especially recommended for binary packages to ensure reproducibility, and is more -# commonly ignored for libraries. -# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control -#poetry.lock -#poetry.toml - -# pdm -# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. -# pdm recommends including project-wide configuration in pdm.toml, but excluding .pdm-python. -# https://pdm-project.org/en/latest/usage/project/#working-with-version-control -#pdm.lock -#pdm.toml -.pdm-python -.pdm-build/ - -# pixi -# Similar to Pipfile.lock, it is generally recommended to include pixi.lock in version control. -#pixi.lock -# Pixi creates a virtual environment in the .pixi directory, just like venv module creates one -# in the .venv directory. It is recommended not to include this directory in version control. -.pixi - -# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm -__pypackages__/ - -# Celery stuff -celerybeat-schedule -celerybeat.pid - -# SageMath parsed files -*.sage.py - -# Environments +# 忽略虚拟环境 .env .envrc .venv @@ -144,64 +126,41 @@ ENV/ env.bak/ venv.bak/ -# Spyder project settings +# 忽略Spyder项目设置 .spyderproject .spyproject -# Rope project settings +# 忽略Rope项目设置 .ropeproject -# mkdocs documentation -/site - -# mypy +# 忽略类型检查缓存 .mypy_cache/ .dmypy.json dmypy.json -# Pyre type checker +# 忽略Pyre类型检查器 .pyre/ -# pytype static type analyzer +# 忽略pytype静态类型分析器 .pytype/ -# Cython debug symbols +# 忽略Cython调试符号 cython_debug/ -# PyCharm -# JetBrains specific template is maintained in a separate JetBrains.gitignore that can -# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore -# and can be added to the global gitignore or merged into this file. For a more nuclear -# option (not recommended) you can uncomment the following to ignore the entire idea folder. -#.idea/ - -# Abstra -# Abstra is an AI-powered process automation framework. -# Ignore directories containing user credentials, local state, and settings. -# Learn more at https://abstra.io/docs +# 忽略Abstra相关文件 .abstra/ -# Visual Studio Code -# Visual Studio Code specific template is maintained in a separate VisualStudioCode.gitignore -# that can be found at https://github.com/github/gitignore/blob/main/Global/VisualStudioCode.gitignore -# and can be added to the global gitignore or merged into this file. However, if you prefer, -# you could uncomment the following to ignore the entire vscode folder -# .vscode/ - -# Ruff stuff: +# 忽略Ruff缓存 .ruff_cache/ -# PyPI configuration file +# 忽略PyPI配置文件 .pypirc -# Cursor -# Cursor is an AI-powered code editor. `.cursorignore` specifies files/directories to -# exclude from AI features like autocomplete and code analysis. Recommended for sensitive data -# refer to https://docs.cursor.com/context/ignore-files +# 忽略Cursor相关文件 .cursorignore .cursorindexingignore -# Marimo +# 忽略Marimo相关文件 marimo/_static/ marimo/_lsp/ -__marimo__/ +__marimo__/ \ No newline at end of file diff --git a/README.en_us.md b/README.en_us.md new file mode 100644 index 0000000..bc53683 --- /dev/null +++ b/README.en_us.md @@ -0,0 +1,527 @@ +# Oraset Programming Language + +Oraset is a simple cross-platform programming language implemented in Python, supporting both Windows and Linux systems. + +## Features + +- Cross-platform operation (Windows and Linux) +- Simple and easy-to-understand syntax +- Support for variable definition and assignment +- Support for expression evaluation (arithmetic, comparison, logical) +- Support for conditional statements (if, else if, else) +- Support for loop statements (while) +- Support for function definition and call +- Support for class definition +- Support for output functionality +- Support for comments + +## Quick Start + +### Runtime Environment + +- Python 3.6+ + +### How to Run + +```bash +# Run .oas or .oraset file +python oraset.py + +# Pack .oas or .oraset file into .osp file +python oraset.py pack + +# Run .osp file +python oraset.py -osp +``` + +### Example + +Create a file named `hello.oas` with the following content: + +```oraset +!! Output Hello, Oraset! +show(`Hello, Oraset!`); + +!! Variable definition and calculation +x = 10; +y = 20; +sum = x + y; +show(`10 + 20 = `, sum); + +!! Conditional statement +if (sum > 25) { + show(`Sum is greater than 25`); +} else { + show(`Sum is less than or equal to 25`); +} + +!! Loop statement +i = 0; +while (i < 5) { + show(`i = `, i); + i = i + 1; +} + +!! Function definition +def add(a, b) { + return a + b; +} + +!! Function call +result = add(5, 6); +show(`5 + 6 = `, result); + +!! Class definition +class Calculator { + def multiply(a, b) { + return a * b; + } +} +``` + +Run: + +```bash +python oraset.py hello.oas +``` + +## Syntax Guide + +### Comments + +Use `!!` to start a single-line comment: + +```oraset +!! This is a comment +``` + +Use `!*` and `*!` to enclose multi-line comments: + +```oraset +!* This is a +multi-line comment *! +``` + +### Built-in Library Functions + +Oraset provides the following built-in library functions: + +#### Math Functions + +| Function | Description | Example | +|---------|-------------|--------| +| `abs(x)` | Returns the absolute value of x | `abs(-10)` → `10` | +| `max(x, y)` | Returns the maximum of x and y | `max(10, 20)` → `20` | +| `min(x, y)` | Returns the minimum of x and y | `min(10, 20)` → `10` | +| `pow(x, y)` | Returns x raised to the power of y | `pow(2, 3)` → `8` | +| `sqrt(x)` | Returns the square root of x (integer part) | `sqrt(16)` → `4` | + +#### String Functions + +| Function | Description | Example | +|---------|-------------|--------| +| `len(s)` | Returns the length of string s | `len("Hello")` → `5` | +| `upper(s)` | Converts string s to uppercase | `upper("Hello")` → `HELLO` | +| `lower(s)` | Converts string s to lowercase | `lower("HELLO")` → `hello` | +| `substring(s, start, end)` | Returns the substring of s from start to end | `substring("Hello", 0, 3)` → `Hel` | +| `startsWith(s, prefix)` | Checks if string s starts with prefix | `startsWith("Hello", "He")` → `1` | +| `endsWith(s, suffix)` | Checks if string s ends with suffix | `endsWith("Hello", "lo")` → `1` | +| `replace(s, old, new)` | Replaces old with new in string s | `replace("Hello", "He", "Hi")` → `Hiello` | +| `split(s, sep)` | Splits string s by separator sep | `split("a,b,c", ",")` → `["a", "b", "c"]` | +| `trim(s)` | Removes whitespace from both ends of string s | `trim(" Hello ")` → `Hello` | + +#### Array Functions + +| Function | Description | Example | +|---------|-------------|--------| +| `array(...items)` | Creates an array with the specified elements | `array("a", "b", "c")` → `["a", "b", "c"]` | +| `push(arr, item)` | Adds an element to the array | `push(["a", "b"], "c")` → `["a", "b", "c"]` | +| `pop(arr)` | Removes the last element from the array | `pop(["a", "b", "c"])` → `["a", "b"]` | +| `indexOf(arr, item)` | Returns the index of item in array arr, or -1 if not found | `indexOf(["a", "b", "c"], "b")` → `1` | + +#### Type Check Functions + +| Function | Description | Example | +|---------|-------------|--------| +| `is_number(x)` | Checks if x is a number | `is_number("123")` → `1` | +| `is_string(x)` | Checks if x is a string | `is_string("Hello")` → `1` | + +#### Other Functions + +| Function | Description | Example | +|---------|-------------|--------| +| `input(prompt)` | Gets input from the user | `input("Enter a value: ")` | +| `int(s)` | Converts string s to an integer | `int("123")` → `123` | +| `str(x)` | Converts x to a string | `str(123)` → `"123"` | + +#### File Operation Functions + +| Function | Description | Example | +|---------|-------------|--------| +| `file_read(filename)` | Reads file content | `file_read("test.txt")` | +| `file_write(filename, content)` | Writes content to file | `file_write("test.txt", "Hello")` | +| `file_exists(filename)` | Checks if file exists | `file_exists("test.txt")` → `1` (exists) or `0` (does not exist) | + +#### Time Functions + +| Function | Description | Example | +|---------|-------------|--------| +| `time_now()` | Gets current timestamp | `time_now()` | +| `time_sleep(seconds)` | Sleeps for specified seconds | `time_sleep(2)` | + +#### Random Functions + +| Function | Description | Example | +|---------|-------------|--------| +| `random_int(min, max)` | Generates random integer between min and max | `random_int(1, 100)` | +| `random_float()` | Generates random float between 0 and 1 | `random_float()` | + +#### System Functions + +| Function | Description | Example | +|---------|-------------|--------| +| `system(command)` | Executes system command | `system("dir")` | +| `get_env(var_name)` | Gets environment variable | `get_env("PATH")` | + + + +### Math Module + +**Note: Using math functions requires importing the math module first** + +```oraset +import math +``` + +| Function | Description | Example | +|---------|-------------|--------| +| `abs(x)` | Returns the absolute value of x | `abs(-10)` → `10.0` | +| `max(x, y)` | Returns the maximum of x and y | `max(10, 20)` → `20.0` | +| `min(x, y)` | Returns the minimum of x and y | `min(10, 20)` → `10.0` | +| `pow(x, y)` | Returns x raised to the power of y | `pow(2, 3)` → `8.0` | +| `sqrt(x)` | Returns the square root of x | `sqrt(16)` → `4.0` | +| `sin(x)` | Returns the sine of x (in degrees) | `sin(90)` → `1.0` | +| `cos(x)` | Returns the cosine of x (in degrees) | `cos(0)` → `1.0` | +| `tan(x)` | Returns the tangent of x (in degrees) | `tan(45)` → `1.0` | +| `asin(x)` | Returns the arcsine of x (in degrees) | `asin(1)` → `90.0` | +| `acos(x)` | Returns the arccosine of x (in degrees) | `acos(1)` → `0.0` | +| `atan(x)` | Returns the arctangent of x (in degrees) | `atan(1)` → `45.0` | +| `pi()` | Returns the value of pi | `pi()` → `3.141592653589793` | +| `e()` | Returns the value of e | `e()` → `2.718281828459045` | + +#### Network Request Functions + +**Note: Using network request functions requires importing the http module first** + +```oraset +import http +``` + +| Function | Description | Example | +|---------|-------------|--------| +| `http_get(url)` | Sends an HTTP GET request and returns the response | `http_get("http://example.com")` | +| `http_post(url, data)` | Sends an HTTP POST request and returns the response | `http_post("http://httpbin.org/post", "{\"name\": \"Oraset\", \"version\": \"1.0\"}")` | + +### Plugin System + +Oraset supports user-defined plugins. You can place your extension functions in the `includes` folder. + +#### Creating a Plugin + +1. Create a file with `.oraset` extension in the `includes` folder +2. Define functions, classes, etc. in the file +3. Load the plugin using `import plugin_name` directive + +#### Example + +Create `includes/math_utils.oraset` file: + +```oraset +!! Math utilities plugin + +def add(a, b) { + return a + b; +} + +def subtract(a, b) { + return a - b; +} +``` + +Use it in your code: + +```oraset +import math_utils + +result = add(10, 20); +show(`10 + 20 = `, result); +``` + +### Variables + +Variable definition and assignment: + +```oraset +x = 10; // Integer +message = `Hello`; // String +``` + +### Data Types + +- Integers: `10`, `20`, `30` +- Strings: `` `Hello` ``, `` `World` `` + +Strings support the following escape sequences: + +| Escape Sequence | Description | +|----------------|-------------| +| `\n` | Newline | +| `\t` | Tab | +| `\r` | Carriage return | +| `\\` | Backslash | +| `` \` `` | Backtick | + +Examples: + +```oraset +// Output multi-line text +show(`Line 1\nLine 2`); + +// Output text with tabs +show(`Column 1\tColumn 2`); + +// Output text with backslashes +show(`Path: C:\\Users\\Name`); +``` + +### Operators + +#### Arithmetic Operators + +- `+`:Addition +- `-`:Subtraction +- `*`:Multiplication +- `/`:Division (integer division) + +#### Comparison Operators + +- `==`:Equal to +- `!=`:Not equal to +- `<`:Less than +- `<=`:Less than or equal to +- `>`:Greater than +- `>=`:Greater than or equal to + +#### Logical Operators + +- `&&`:Logical AND +- `||`:Logical OR + +#### Assignment Operators + +- `=`:Assignment +- `+=`:Add assignment +- `-=`:Subtract assignment +- `*=`:Multiply assignment +- `/=`:Divide assignment + +### Conditional Statements + +```oraset +if (condition) { + // Code block +} else if (condition) { + // Code block +} else { + // Code block +} +``` + +### Loop Statements + +```oraset +while (condition) { + // Code block +} +``` + +### Function Definition + +```oraset +def function_name(parameter1, parameter2, ...) { + // Code block + return value; +} +``` + +### Function Call + +```oraset +result = function_name(arg1, arg2, ...); +``` + +### Class Definition + +```oraset +class ClassName { + def method_name(parameter1, parameter2, ...) { + // Code block + return value; + } +} +``` + +### Output + +Use the `show` function to output content: + +```oraset +show(`Hello`); +show(`The value is `, x); +``` + +## Example Code + +### Basic Example + +```oraset +// Variables and expressions +x = 10; +y = 20; +sum = x + y; +show(`Sum: `, sum); + +// Conditional statements +if (sum > 25) { + show(`Sum is greater than 25`); +} else if (sum == 25) { + show(`Sum is exactly 25`); +} else { + show(`Sum is less than 25`); +} + +// Loop statements +i = 0; +while (i < 5) { + show(`i = `, i); + i += 1; +} +``` + +### Function Example + +```oraset +// Function definition +def add(a, b) { + return a + b; +} + +def multiply(a, b) { + return a * b; +} + +// Function call +result1 = add(10, 20); +result2 = multiply(5, 6); +show(`10 + 20 = `, result1); +show(`5 * 6 = `, result2); + +// Recursive function +def factorial(n) { + if (n == 0 || n == 1) { + return 1; + } + return n * factorial(n - 1); +} + +result = factorial(5); +show(`Factorial of 5 is `, result); +``` + +### Class Example + +```oraset +// Class definition +class Calculator { + def add(a, b) { + return a + b; + } + + def subtract(a, b) { + return a - b; + } + + def multiply(a, b) { + return a * b; + } + + def divide(a, b) { + return a / b; + } +} + +// Note: Class method calls need to be implemented through functions +// In the current version, class definitions are stored but not directly instantiated +``` + +## Project Structure + +``` +Oraset/ +├── oraset.py # Core interpreter +├── examples/ # Example code +│ ├── hello.oas # Basic example +│ ├── hello.osp # Packed basic example +│ ├── functions.oas # Function example +│ └── classes.oas # Class example +├── README.md # This tutorial (Chinese) +└── README.en_us.md # This tutorial (English) +``` + +## .osp File Description + +.osp files are the packed file format for Oraset, generated by compressing and encoding .oas files. They have the following features: + +- **Encryption**: .osp files use base64 encoding and zlib compression, making the source code not easily viewable directly +- **Cross-platform**: .osp files can be run directly on both Windows and Linux systems +- **Convenience**: .osp files are standalone files that can be easily distributed and used + +### Packing .oas Files + +Use the `pack` command to pack .oas files into .osp files: + +```bash +python oraset.py pack +``` + +This will generate a file with the same name as the .oas file but with the .osp extension. + +### Running .osp Files + +Use the `-osp` parameter to run .osp files: + +```bash +python oraset.py -osp +``` + +This will automatically decompress and execute the code in the .osp file. + +## Development Notes + +Oraset is implemented in Python and mainly consists of the following components: + +- **Lexer**: Lexical analyzer, responsible for converting source code into tokens +- **Parser**: Syntax analyzer, responsible for building abstract syntax tree (AST) +- **Interpreter**: Interpreter, responsible for executing AST and generating results + +## Future Plans + +- Support more data types (floating-point numbers, booleans, etc.) +- Improve class instantiation and method calls +- Support module import +- Add standard library +- Optimize performance + +## License + +GNU General Public License v3.0 diff --git a/README.md b/README.md new file mode 100644 index 0000000..fb98c2c --- /dev/null +++ b/README.md @@ -0,0 +1,527 @@ +# Oraset 编程语言 + +Oraset是一种简单的跨平台编程语言,使用Python实现,支持Windows和Linux系统。 + +## 特性 + +- 跨平台运行(Windows和Linux) +- 简洁易懂的语法 +- 支持变量定义和赋值 +- 支持表达式计算(算术、比较、逻辑) +- 支持条件语句(if、else if、else) +- 支持循环语句(while) +- 支持函数定义和调用 +- 支持类定义 +- 支持输出功能 +- 支持注释 + +## 快速开始 + +### 运行环境 + +- Python 3.6+ + +### 运行方式 + +```bash +# 运行.oas或.oraset文件 +python oraset.py + +# 将.oas或.oraset文件打包成.osp文件 +python oraset.py pack + +# 运行.osp文件 +python oraset.py -osp +``` + +### 示例 + +创建一个名为`hello.oas`的文件,内容如下: + +```oraset +!! 输出Hello, Oraset! +show(`Hello, Oraset!`); + +!! 变量定义和计算 +x = 10; +y = 20; +sum = x + y; +show(`10 + 20 = `, sum); + +!! 条件语句 +if (sum > 25) { + show(`Sum is greater than 25`); +} else { + show(`Sum is less than or equal to 25`); +} + +!! 循环语句 +i = 0; +while (i < 5) { + show(`i = `, i); + i = i + 1; +} + +!! 函数定义 +def add(a, b) { + return a + b; +} + +!! 函数调用 +result = add(5, 6); +show(`5 + 6 = `, result); + +!! 类定义 +class Calculator { + def multiply(a, b) { + return a * b; + } +} +``` + +运行: + +```bash +python oraset.py hello.oas +``` + +## 语法指南 + +### 注释 + +使用`!!`开始单行注释: + +```oraset +!! 这是一个注释 +``` + +使用`!*`和`*!`包围多行注释: + +```oraset +!* 这是一个 +多行注释 *! +``` + +### 内置库函数 + +Oraset提供了以下内置库函数: + +#### 数学函数 + +| 函数名 | 描述 | 示例 | +|-------|------|------| +| `abs(x)` | 返回x的绝对值 | `abs(-10)` → `10` | +| `max(x, y)` | 返回x和y中的最大值 | `max(10, 20)` → `20` | +| `min(x, y)` | 返回x和y中的最小值 | `min(10, 20)` → `10` | +| `pow(x, y)` | 返回x的y次方 | `pow(2, 3)` → `8` | +| `sqrt(x)` | 返回x的平方根(取整数部分) | `sqrt(16)` → `4` | + +#### 字符串函数 + +| 函数名 | 描述 | 示例 | +|-------|------|------| +| `len(s)` | 返回字符串s的长度 | `len("Hello")` → `5` | +| `upper(s)` | 将字符串s转换为大写 | `upper("Hello")` → `HELLO` | +| `lower(s)` | 将字符串s转换为小写 | `lower("HELLO")` → `hello` | +| `substring(s, start, end)` | 返回字符串s从start到end的子串 | `substring("Hello", 0, 3)` → `Hel` | +| `startsWith(s, prefix)` | 检查字符串s是否以prefix开头 | `startsWith("Hello", "He")` → `1` | +| `endsWith(s, suffix)` | 检查字符串s是否以suffix结尾 | `endsWith("Hello", "lo")` → `1` | +| `replace(s, old, new)` | 替换字符串s中的old为new | `replace("Hello", "He", "Hi")` → `Hiello` | +| `split(s, sep)` | 以sep为分隔符分割字符串s | `split("a,b,c", ",")` → `["a", "b", "c"]` | +| `trim(s)` | 去除字符串s首尾的空白 | `trim(" Hello ")` → `Hello` | + +#### 数组函数 + +| 函数名 | 描述 | 示例 | +|-------|------|------| +| `array(...items)` | 创建一个包含指定元素的数组 | `array("a", "b", "c")` → `["a", "b", "c"]` | +| `push(arr, item)` | 向数组arr添加一个元素 | `push(["a", "b"], "c")` → `["a", "b", "c"]` | +| `pop(arr)` | 从数组arr中移除最后一个元素 | `pop(["a", "b", "c"])` → `["a", "b"]` | +| `indexOf(arr, item)` | 返回元素item在数组arr中的索引,若不存在则返回-1 | `indexOf(["a", "b", "c"], "b")` → `1` | + +#### 类型检查函数 + +| 函数名 | 描述 | 示例 | +|-------|------|------| +| `is_number(x)` | 检查x是否为数字 | `is_number("123")` → `1` | +| `is_string(x)` | 检查x是否为字符串 | `is_string("Hello")` → `1` | + +#### 其他函数 + +| 函数名 | 描述 | 示例 | +|-------|------|------| +| `input(prompt)` | 从用户输入获取值 | `input("请输入: ")` | +| `int(s)` | 将字符串s转换为整数 | `int("123")` → `123` | +| `str(x)` | 将x转换为字符串 | `str(123)` → `"123"` | + +#### 文件操作函数 + +| 函数名 | 描述 | 示例 | +|-------|------|------| +| `file_read(filename)` | 读取文件内容 | `file_read("test.txt")` | +| `file_write(filename, content)` | 写入内容到文件 | `file_write("test.txt", "Hello")` | +| `file_exists(filename)` | 检查文件是否存在 | `file_exists("test.txt")` → `1` (存在) 或 `0` (不存在) | + +#### 时间函数 + +| 函数名 | 描述 | 示例 | +|-------|------|------| +| `time_now()` | 获取当前时间戳 | `time_now()` | +| `time_sleep(seconds)` | 暂停指定秒数 | `time_sleep(2)` | + +#### 随机数函数 + +| 函数名 | 描述 | 示例 | +|-------|------|------| +| `random_int(min, max)` | 生成指定范围内的随机整数 | `random_int(1, 100)` | +| `random_float()` | 生成0-1之间的随机浮点数 | `random_float()` | + +#### 系统函数 + +| 函数名 | 描述 | 示例 | +|-------|------|------| +| `system(command)` | 执行系统命令 | `system("dir")` | +| `get_env(var_name)` | 获取环境变量 | `get_env("PATH")` | + + + +### 数学模块 + +**注意:使用数学函数需要先导入math模块** + +```oraset +import math +``` + +| 函数名 | 描述 | 示例 | +|-------|------|------| +| `abs(x)` | 返回x的绝对值 | `abs(-10)` → `10.0` | +| `max(x, y)` | 返回x和y中的最大值 | `max(10, 20)` → `20.0` | +| `min(x, y)` | 返回x和y中的最小值 | `min(10, 20)` → `10.0` | +| `pow(x, y)` | 返回x的y次方 | `pow(2, 3)` → `8.0` | +| `sqrt(x)` | 返回x的平方根 | `sqrt(16)` → `4.0` | +| `sin(x)` | 返回x(度)的正弦值 | `sin(90)` → `1.0` | +| `cos(x)` | 返回x(度)的余弦值 | `cos(0)` → `1.0` | +| `tan(x)` | 返回x(度)的正切值 | `tan(45)` → `1.0` | +| `asin(x)` | 返回x的反正弦值(度) | `asin(1)` → `90.0` | +| `acos(x)` | 返回x的反余弦值(度) | `acos(1)` → `0.0` | +| `atan(x)` | 返回x的反正切值(度) | `atan(1)` → `45.0` | +| `pi()` | 返回圆周率π的值 | `pi()` → `3.141592653589793` | +| `e()` | 返回自然常数e的值 | `e()` → `2.718281828459045` | + +#### 网络请求函数 + +**注意:使用网络请求函数需要先导入http模块** + +```oraset +import http +``` + +| 函数名 | 描述 | 示例 | +|-------|------|------| +| `http_get(url)` | 发送HTTP GET请求并返回响应内容 | `http_get("http://example.com")` | +| `http_post(url, data)` | 发送HTTP POST请求并返回响应内容 | `http_post("http://httpbin.org/post", "{\"name\": \"Oraset\", \"version\": \"1.0\"}")` | + +### 插件系统 + +Oraset支持用户自定义插件,您可以将自己的扩展功能放在`includes`文件夹中。 + +#### 创建插件 + +1. 在`includes`文件夹中创建一个以`.oraset`为后缀的文件 +2. 在文件中定义函数、类等 +3. 使用`import 插件名`指令加载插件 + +#### 示例 + +创建`includes/math_utils.oraset`文件: + +```oraset +!! 数学工具插件 + +def add(a, b) { + return a + b; +} + +def subtract(a, b) { + return a - b; +} +``` + +在您的代码中使用: + +```oraset +import math_utils + +result = add(10, 20); +show(`10 + 20 = `, result); +``` + +### 变量 + +变量定义和赋值: + +```oraset +x = 10; // 整数 +message = `Hello`; // 字符串 +``` + +### 数据类型 + +- 整数:`10`, `20`, `30` +- 字符串:`` `Hello` ``, `` `World` `` + +字符串支持以下转义符: + +| 转义符 | 描述 | +|-------|------| +| `\n` | 换行符 | +| `\t` | 制表符 | +| `\r` | 回车符 | +| `\\` | 反斜杠 | +| `` \` `` | 反引号 | + +示例: + +```oraset +// 输出多行文本 +show(`Line 1\nLine 2`); + +// 输出带制表符的文本 +show(`Column 1\tColumn 2`); + +// 输出包含反斜杠的文本 +show(`Path: C:\\Users\\Name`); +``` + +### 运算符 + +#### 算术运算符 + +- `+`:加法 +- `-`:减法 +- `*`:乘法 +- `/`:除法(整数除法) + +#### 比较运算符 + +- `==`:等于 +- `!=`:不等于 +- `<`:小于 +- `<=`:小于等于 +- `>`:大于 +- `>=`:大于等于 + +#### 逻辑运算符 + +- `&&`:逻辑与 +- `||`:逻辑或 + +#### 赋值运算符 + +- `=`:赋值 +- `+=`:加赋值 +- `-=`:减赋值 +- `*=`:乘赋值 +- `/=`:除赋值 + +### 条件语句 + +```oraset +if (condition) { + // 代码块 +} else if (condition) { + // 代码块 +} else { + // 代码块 +} +``` + +### 循环语句 + +```oraset +while (condition) { + // 代码块 +} +``` + +### 函数定义 + +```oraset +def function_name(parameter1, parameter2, ...) { + // 代码块 + return value; +} +``` + +### 函数调用 + +```oraset +result = function_name(arg1, arg2, ...); +``` + +### 类定义 + +```oraset +class ClassName { + def method_name(parameter1, parameter2, ...) { + // 代码块 + return value; + } +} +``` + +### 输出 + +使用`show`函数输出内容: + +```oraset +show(`Hello`); +show(`The value is `, x); +``` + +## 示例代码 + +### 基础示例 + +```oraset +// 变量和表达式 +x = 10; +y = 20; +sum = x + y; +show(`Sum: `, sum); + +// 条件语句 +if (sum > 25) { + show(`Sum is greater than 25`); +} else if (sum == 25) { + show(`Sum is exactly 25`); +} else { + show(`Sum is less than 25`); +} + +// 循环语句 +i = 0; +while (i < 5) { + show(`i = `, i); + i += 1; +} +``` + +### 函数示例 + +```oraset +// 函数定义 +def add(a, b) { + return a + b; +} + +def multiply(a, b) { + return a * b; +} + +// 函数调用 +result1 = add(10, 20); +result2 = multiply(5, 6); +show(`10 + 20 = `, result1); +show(`5 * 6 = `, result2); + +// 递归函数 +def factorial(n) { + if (n == 0 || n == 1) { + return 1; + } + return n * factorial(n - 1); +} + +result = factorial(5); +show(`Factorial of 5 is `, result); +``` + +### 类示例 + +```oraset +// 类定义 +class Calculator { + def add(a, b) { + return a + b; + } + + def subtract(a, b) { + return a - b; + } + + def multiply(a, b) { + return a * b; + } + + def divide(a, b) { + return a / b; + } +} + +// 注意:类方法的调用需要通过函数实现 +// 目前版本中,类定义会被存储但不会直接实例化 +``` + +## 项目结构 + +``` +Oraset/ +├── oraset.py # 核心解释器 +├── examples/ # 示例代码 +│ ├── hello.oas # 基础示例 +│ ├── hello.osp # 打包后的基础示例 +│ ├── functions.oas # 函数示例 +│ └── classes.oas # 类示例 +├── README.md # 本教程(中文) +└── README.en_us.md # 本教程(英文) +``` + +## .osp文件说明 + +.osp文件是Oraset的打包文件格式,它是通过对.oas文件进行压缩和编码生成的,具有以下特点: + +- **加密性**:.osp文件使用base64编码和zlib压缩,使得源代码不易被直接查看 +- **跨平台**:.osp文件可以在Windows和Linux系统上直接运行 +- **便捷性**:.osp文件是一个独立的文件,可以方便地分发和使用 + +### 打包.oas文件 + +使用`pack`命令将.oas文件打包成.osp文件: + +```bash +python oraset.py pack +``` + +这将生成一个与.oas文件同名但扩展名为.osp的文件。 + +### 运行.osp文件 + +使用`-osp`参数运行.osp文件: + +```bash +python oraset.py -osp +``` + +这将自动解压和执行.osp文件中的代码。 + +## 开发说明 + +Oraset使用Python实现,主要包含以下组件: + +- **Lexer**:词法分析器,负责将源代码转换为词法单元 +- **Parser**:语法分析器,负责构建抽象语法树(AST) +- **Interpreter**:解释器,负责执行AST并生成结果 + +## 未来计划 + +- 支持更多数据类型(浮点数、布尔值等) +- 完善类的实例化和方法调用 +- 支持模块导入 +- 添加标准库 +- 优化性能 + +## 许可证 + +GNU General Public License v3.0 diff --git a/oraset.py b/oraset.py new file mode 100644 index 0000000..f769852 --- /dev/null +++ b/oraset.py @@ -0,0 +1,1424 @@ +#!/usr/bin/env python3 + +import sys + +class Token: + def __init__(self, type_, value, line, column): + self.type = type_ + self.value = value + self.line = line + self.column = column + + def __str__(self): + return f"Token({self.type}, {repr(self.value)}, {self.line}, {self.column})" + +class Lexer: + def __init__(self, source): + self.source = source + self.position = 0 + self.line = 1 + self.column = 1 + + def get_next_token(self): + while self.position < len(self.source): + char = self.source[self.position] + + # Skip whitespace + if char.isspace(): + if char == '\n': + self.line += 1 + self.column = 1 + else: + self.column += 1 + self.position += 1 + continue + + # Skip comments (!! for single line, !* ... *! for multi-line) + if char == '!' and self.position + 1 < len(self.source): + # Check for single-line comment (!!) + if self.source[self.position + 1] == '!': + self.position += 2 + self.column += 2 + while self.position < len(self.source) and self.source[self.position] != '\n': + self.position += 1 + self.column += 1 + continue + # Check for multi-line comment (!* ... *!) + elif self.source[self.position + 1] == '*': + self.position += 2 + self.column += 2 + # Skip until closing *! + while self.position + 1 < len(self.source) and not (self.source[self.position] == '*' and self.source[self.position + 1] == '!'): + if self.source[self.position] == '\n': + self.line += 1 + self.column = 1 + else: + self.column += 1 + self.position += 1 + if self.position + 1 < len(self.source): + self.position += 2 + self.column += 2 + continue + # Include directive + elif char == '!' and self.position + 7 < len(self.source) and self.source[self.position:self.position+8] == '!include': + self.position += 8 + self.column += 8 + # Skip whitespace + while self.position < len(self.source) and self.source[self.position].isspace(): + self.position += 1 + self.column += 1 + # Read module name in quotes + if self.position < len(self.source) and self.source[self.position] == "'": + self.position += 1 + self.column += 1 + start = self.position + while self.position < len(self.source) and self.source[self.position] != "'": + self.position += 1 + self.column += 1 + if self.position < len(self.source): + self.position += 1 + self.column += 1 + module = self.source[start:self.position-1] + return Token('INCLUDE_DIRECTIVE', module, self.line, self.column - len(module) - 2) + + # String literal + if char == '`': + start = self.position + self.position += 1 + self.column += 1 + value = '' + while self.position < len(self.source) and self.source[self.position] != '`': + if self.source[self.position] == '\\': + # Handle escape sequences + self.position += 1 + self.column += 1 + if self.position < len(self.source): + if self.source[self.position] == 'n': + value += '\n' + elif self.source[self.position] == 't': + value += '\t' + elif self.source[self.position] == 'r': + value += '\r' + elif self.source[self.position] == '\\': + value += '\\' + elif self.source[self.position] == '`': + value += '`' + else: + # Unknown escape sequence, just add the character + value += self.source[self.position] + self.position += 1 + self.column += 1 + elif self.source[self.position] == '\n': + value += '\n' + self.line += 1 + self.column = 1 + self.position += 1 + else: + value += self.source[self.position] + self.column += 1 + self.position += 1 + if self.position < len(self.source): + self.position += 1 + self.column += 1 + return Token('STRING', value, self.line, self.column - len(value) - 2) + + # Negative number + if char == '-' and self.position + 1 < len(self.source) and self.source[self.position + 1].isdigit(): + start = self.position + self.position += 1 + self.column += 1 + while self.position < len(self.source) and self.source[self.position].isdigit(): + self.position += 1 + self.column += 1 + value = self.source[start:self.position] + return Token('INT', value, self.line, self.column - len(value)) + # Number + elif char.isdigit(): + start = self.position + while self.position < len(self.source) and self.source[self.position].isdigit(): + self.position += 1 + self.column += 1 + value = self.source[start:self.position] + return Token('INT', value, self.line, self.column - len(value)) + + # Identifier or keyword + if char.isalpha() or char == '_': + start = self.position + while self.position < len(self.source) and (self.source[self.position].isalnum() or self.source[self.position] == '_'): + self.position += 1 + self.column += 1 + value = self.source[start:self.position] + if value == 'show': + return Token('KEYWORD_SHOW', value, self.line, self.column - len(value)) + elif value == 'if': + return Token('KEYWORD_IF', value, self.line, self.column - len(value)) + elif value == 'else': + return Token('KEYWORD_ELSE', value, self.line, self.column - len(value)) + elif value == 'while': + return Token('KEYWORD_WHILE', value, self.line, self.column - len(value)) + elif value == 'def': + return Token('KEYWORD_DEF', value, self.line, self.column - len(value)) + elif value == 'class': + return Token('KEYWORD_CLASS', value, self.line, self.column - len(value)) + elif value == 'return': + return Token('KEYWORD_RETURN', value, self.line, self.column - len(value)) + elif value == 'import': + return Token('KEYWORD_IMPORT', value, self.line, self.column - len(value)) + else: + return Token('IDENTIFIER', value, self.line, self.column - len(value)) + + # Operators and punctuation + if char == '+' and self.position + 1 < len(self.source) and self.source[self.position + 1] == '+': + self.position += 2 + self.column += 2 + return Token('PLUS_PLUS', '++', self.line, self.column - 2) + elif char == '-' and self.position + 1 < len(self.source) and self.source[self.position + 1] == '-': + self.position += 2 + self.column += 2 + return Token('MINUS_MINUS', '--', self.line, self.column - 2) + elif char == '+' and self.position + 1 < len(self.source) and self.source[self.position + 1] == '=': + self.position += 2 + self.column += 2 + return Token('PLUS_EQUALS', '+=', self.line, self.column - 2) + elif char == '-' and self.position + 1 < len(self.source) and self.source[self.position + 1] == '=': + self.position += 2 + self.column += 2 + return Token('MINUS_EQUALS', '-=', self.line, self.column - 2) + elif char == '*' and self.position + 1 < len(self.source) and self.source[self.position + 1] == '=': + self.position += 2 + self.column += 2 + return Token('MULTIPLY_EQUALS', '*=', self.line, self.column - 2) + elif char == '/' and self.position + 1 < len(self.source) and self.source[self.position + 1] == '=': + self.position += 2 + self.column += 2 + return Token('DIVIDE_EQUALS', '/=', self.line, self.column - 2) + elif char == '=' and self.position + 1 < len(self.source) and self.source[self.position + 1] == '=': + self.position += 2 + self.column += 2 + return Token('EQUALS', '==', self.line, self.column - 2) + elif char == '=': + self.position += 1 + self.column += 1 + return Token('ASSIGN', '=', self.line, self.column - 1) + elif char == '!' and self.position + 1 < len(self.source) and self.source[self.position + 1] == '=': + self.position += 2 + self.column += 2 + return Token('NOT_EQUALS', '!=', self.line, self.column - 2) + elif char == '@': + self.position += 1 + self.column += 1 + return Token('AT', '@', self.line, self.column - 1) + elif char == '.': + self.position += 1 + self.column += 1 + return Token('DOT', '.', self.line, self.column - 1) + elif char == '<' and self.position + 1 < len(self.source) and self.source[self.position + 1] == '=': + self.position += 2 + self.column += 2 + return Token('LESS_EQUAL', '<=', self.line, self.column - 2) + elif char == '>' and self.position + 1 < len(self.source) and self.source[self.position + 1] == '=': + self.position += 2 + self.column += 2 + return Token('GREATER_EQUAL', '>=', self.line, self.column - 2) + elif char == '&' and self.position + 1 < len(self.source) and self.source[self.position + 1] == '&': + self.position += 2 + self.column += 2 + return Token('AND', '&&', self.line, self.column - 2) + elif char == '|' and self.position + 1 < len(self.source) and self.source[self.position + 1] == '|': + self.position += 2 + self.column += 2 + return Token('OR', '||', self.line, self.column - 2) + elif char == '@': + # Check if it's an include directive + if self.position + 7 < len(self.source) and self.source[self.position+1:self.position+8] == 'include': + # It's an include directive + # Move past '@include' + self.position += 8 + self.column += 8 + # Skip whitespace after include + while self.position < len(self.source) and self.source[self.position].isspace(): + self.position += 1 + self.column += 1 + # Get the module name + if self.position < len(self.source) and (self.source[self.position].isalpha() or self.source[self.position] == '_'): + start = self.position + while self.position < len(self.source) and (self.source[self.position].isalnum() or self.source[self.position] == '_'): + self.position += 1 + module_name = self.source[start:self.position] + self.column += len(module_name) + return Token('INCLUDE_DIRECTIVE', module_name, self.line, self.column - len(module_name)) + # Otherwise, it's an unknown token + self.position += 1 + self.column += 1 + elif char == '!': + # It's a NOT operator + self.position += 1 + self.column += 1 + return Token('NOT', '!', self.line, self.column - 1) + elif char == '+': + self.position += 1 + self.column += 1 + return Token('PLUS', '+', self.line, self.column - 1) + elif char == '-': + self.position += 1 + self.column += 1 + return Token('MINUS', '-', self.line, self.column - 1) + elif char == '*': + self.position += 1 + self.column += 1 + return Token('MULTIPLY', '*', self.line, self.column - 1) + elif char == '/': + self.position += 1 + self.column += 1 + return Token('DIVIDE', '/', self.line, self.column - 1) + elif char == '=': + self.position += 1 + self.column += 1 + return Token('ASSIGN', '=', self.line, self.column - 1) + elif char == '<': + self.position += 1 + self.column += 1 + return Token('LESS', '<', self.line, self.column - 1) + elif char == '>': + self.position += 1 + self.column += 1 + return Token('GREATER', '>', self.line, self.column - 1) + elif char == '(': + self.position += 1 + self.column += 1 + return Token('LPAREN', '(', self.line, self.column - 1) + elif char == ')': + self.position += 1 + self.column += 1 + return Token('RPAREN', ')', self.line, self.column - 1) + elif char == ';': + self.position += 1 + self.column += 1 + return Token('SEMICOLON', ';', self.line, self.column - 1) + elif char == ',': + self.position += 1 + self.column += 1 + return Token('COMMA', ',', self.line, self.column - 1) + elif char == '{': + self.position += 1 + self.column += 1 + return Token('LBRACE', '{', self.line, self.column - 1) + elif char == '}': + self.position += 1 + self.column += 1 + return Token('RBRACE', '}', self.line, self.column - 1) + elif char == '[': + self.position += 1 + self.column += 1 + return Token('LBRACKET', '[', self.line, self.column - 1) + elif char == ']': + self.position += 1 + self.column += 1 + return Token('RBRACKET', ']', self.line, self.column - 1) + + # Unknown character + self.position += 1 + self.column += 1 + + return Token('EOF', None, self.line, self.column) + +class Parser: + def __init__(self, lexer): + self.lexer = lexer + self.current_token = self.lexer.get_next_token() + + def error(self, message): + # For Windows PowerShell compatibility, use simple error messages without ANSI codes + error_msg = f"Syntax Error at line {self.current_token.line}, column {self.current_token.column}: {message}" + # Add more context information + if hasattr(self.lexer, 'source'): + # Get the line where the error occurred + lines = self.lexer.source.split('\n') + if self.current_token.line - 1 < len(lines): + error_line = lines[self.current_token.line - 1] + # Add the error line to the error message + error_msg += f"\nLine {self.current_token.line}: {error_line}" + # Add a pointer to the error position + error_msg += f"\n" + ' ' * (len(f"Line {self.current_token.line}: ") + self.current_token.column - 1) + "^" + # Add specific error messages for common errors + if "Expected SEMICOLON" in message: + error_msg += "\nHint: Missing semicolon at the end of the statement." + elif "Expected LPAREN" in message: + error_msg += "\nHint: Missing opening parenthesis '('." + elif "Expected RPAREN" in message: + error_msg += "\nHint: Missing closing parenthesis ')'." + elif "Expected LBRACE" in message: + error_msg += "\nHint: Missing opening brace '{'." + elif "Expected RBRACE" in message: + error_msg += "\nHint: Missing closing brace '}'." + raise Exception(error_msg) + + def eat(self, token_type): + if self.current_token.type == token_type: + self.current_token = self.lexer.get_next_token() + else: + self.error(f"Expected {token_type}, got {self.current_token.type}") + + def factor(self): + token = self.current_token + if token.type == 'INT': + self.eat('INT') + return {'type': 'literal', 'value': token.value} + elif token.type == 'STRING': + self.eat('STRING') + return {'type': 'literal', 'value': token.value} + elif token.type == 'IDENTIFIER': + # Check if it's a function call or array index + save_position = self.lexer.position + save_line = self.lexer.line + save_column = self.lexer.column + + # Look ahead to see if it's a function call or array index + is_function_call = False + is_array_index = False + pos = save_position + while pos < len(self.lexer.source) and self.lexer.source[pos].isspace(): + pos += 1 + if pos < len(self.lexer.source): + if self.lexer.source[pos] == '(': + is_function_call = True + elif self.lexer.source[pos] == '[': + is_array_index = True + elif self.lexer.source[pos] == '.': + # Handle dot notation (e.g., math.abs) + is_function_call = True + + # Restore the lexer position + self.lexer.position = save_position + self.lexer.line = save_line + self.lexer.column = save_column + + if is_function_call: + # It's a function call (could be with dot notation) + name = self.current_token.value + self.eat('IDENTIFIER') + + # Handle dot notation + while self.current_token.type == 'DOT': + self.eat('DOT') + name += '.' + self.current_token.value + self.eat('IDENTIFIER') + + self.eat('LPAREN') + arguments = [] + if self.current_token.type != 'RPAREN': + arguments.append(self.expression()) + while self.current_token.type == 'COMMA': + self.eat('COMMA') + arguments.append(self.expression()) + self.eat('RPAREN') + return { + 'type': 'function_call', + 'name': name, + 'arguments': arguments + } + elif is_array_index: + # It's an array index + name = self.current_token.value + self.eat('IDENTIFIER') + self.eat('LBRACKET') + index = self.expression() + self.eat('RBRACKET') + return { + 'type': 'array_index', + 'name': name, + 'index': index + } + else: + # It's a simple identifier + self.eat('IDENTIFIER') + return {'type': 'identifier', 'name': token.value} + elif token.type == 'LPAREN': + self.eat('LPAREN') + expr = self.expression() + self.eat('RPAREN') + return expr + else: + self.error(f"Unexpected token {token.type}") + + def statement(self): + if self.current_token.type == 'INCLUDE_DIRECTIVE': + return self.include_directive() + elif self.current_token.type == 'KEYWORD_SHOW': + return self.function_call() + elif self.current_token.type == 'KEYWORD_IF': + return self.if_statement() + elif self.current_token.type == 'KEYWORD_WHILE': + return self.while_statement() + elif self.current_token.type == 'KEYWORD_DEF': + return self.def_statement() + elif self.current_token.type == 'KEYWORD_CLASS': + return self.class_statement() + elif self.current_token.type == 'KEYWORD_RETURN': + return self.return_statement() + elif self.current_token.type == 'KEYWORD_IMPORT': + return self.import_statement() + elif self.current_token.type == 'AT': + return self.import_statement() + elif self.current_token.type == 'IDENTIFIER': + # Check if it's a function call by looking ahead + # We need to save the current position first + save_position = self.lexer.position + save_line = self.lexer.line + save_column = self.lexer.column + + # Look ahead to see if it's a function call + is_function_call = False + pos = save_position + while pos < len(self.lexer.source) and self.lexer.source[pos].isspace(): + pos += 1 + if pos < len(self.lexer.source) and self.lexer.source[pos] == '(': + is_function_call = True + + # Restore the lexer position + self.lexer.position = save_position + self.lexer.line = save_line + self.lexer.column = save_column + + if is_function_call: + # It's a function call (handled in factor) + # The function call is already parsed as part of the expression + # So we just need to parse the expression and add a semicolon + expr = self.expression() + self.eat('SEMICOLON') + return expr + else: + # It's an assignment + return self.assignment() + else: + self.error(f"Unexpected token {self.current_token.type}") + + def term(self): + node = self.factor() + while self.current_token.type in ('MULTIPLY', 'DIVIDE'): + token = self.current_token + if token.type == 'MULTIPLY': + self.eat('MULTIPLY') + else: + self.eat('DIVIDE') + node = { + 'type': 'binary_op', + 'op': token.value, + 'left': node, + 'right': self.factor() + } + return node + + def expression(self): + node = self.logical_or() + return node + + def logical_or(self): + node = self.logical_and() + while self.current_token.type == 'OR': + token = self.current_token + self.eat('OR') + node = { + 'type': 'binary_op', + 'op': token.value, + 'left': node, + 'right': self.logical_and() + } + return node + + def logical_and(self): + node = self.equality() + while self.current_token.type == 'AND': + token = self.current_token + self.eat('AND') + node = { + 'type': 'binary_op', + 'op': token.value, + 'left': node, + 'right': self.equality() + } + return node + + def equality(self): + node = self.comparison() + while self.current_token.type in ('EQUALS', 'NOT_EQUALS'): + token = self.current_token + if token.type == 'EQUALS': + self.eat('EQUALS') + else: + self.eat('NOT_EQUALS') + node = { + 'type': 'binary_op', + 'op': token.value, + 'left': node, + 'right': self.comparison() + } + return node + + def comparison(self): + node = self.term() + while self.current_token.type in ('PLUS', 'MINUS', 'LESS', 'LESS_EQUAL', 'GREATER', 'GREATER_EQUAL'): + token = self.current_token + if token.type == 'PLUS': + self.eat('PLUS') + elif token.type == 'MINUS': + self.eat('MINUS') + elif token.type == 'LESS': + self.eat('LESS') + elif token.type == 'LESS_EQUAL': + self.eat('LESS_EQUAL') + elif token.type == 'GREATER': + self.eat('GREATER') + else: + self.eat('GREATER_EQUAL') + node = { + 'type': 'binary_op', + 'op': token.value, + 'left': node, + 'right': self.term() + } + return node + + def assignment(self): + token = self.current_token + self.eat('IDENTIFIER') + if self.current_token.type == 'ASSIGN': + self.eat('ASSIGN') + value = self.expression() + # Check if there's a semicolon, but don't require it + if self.current_token.type == 'SEMICOLON': + self.eat('SEMICOLON') + return { + 'type': 'assignment', + 'name': token.value, + 'value': value + } + elif self.current_token.type in ('PLUS_EQUALS', 'MINUS_EQUALS', 'MULTIPLY_EQUALS', 'DIVIDE_EQUALS'): + op_token = self.current_token + if op_token.type == 'PLUS_EQUALS': + self.eat('PLUS_EQUALS') + elif op_token.type == 'MINUS_EQUALS': + self.eat('MINUS_EQUALS') + elif op_token.type == 'MULTIPLY_EQUALS': + self.eat('MULTIPLY_EQUALS') + else: + self.eat('DIVIDE_EQUALS') + value = self.expression() + # Check if there's a semicolon, but don't require it + if self.current_token.type == 'SEMICOLON': + self.eat('SEMICOLON') + # Create compound assignment as binary operation + compound_op = op_token.value[0] # Get the first character (e.g., '+' from '+=') + return { + 'type': 'assignment', + 'name': token.value, + 'value': { + 'type': 'binary_op', + 'op': compound_op, + 'left': {'type': 'identifier', 'name': token.value}, + 'right': value + } + } + else: + self.error(f"Expected assignment operator, got {self.current_token.type}") + + def function_call(self): + token = self.current_token + self.eat('KEYWORD_SHOW') + self.eat('LPAREN') + arguments = [] + if self.current_token.type != 'RPAREN': + arguments.append(self.expression()) + while self.current_token.type == 'COMMA': + self.eat('COMMA') + arguments.append(self.expression()) + self.eat('RPAREN') + # Check if there's a semicolon, but don't require it + if self.current_token.type == 'SEMICOLON': + self.eat('SEMICOLON') + return { + 'type': 'function_call', + 'name': token.value, + 'arguments': arguments + } + + def block(self): + self.eat('LBRACE') + statements = [] + while self.current_token.type != 'RBRACE' and self.current_token.type != 'EOF': + statements.append(self.statement()) + self.eat('RBRACE') + return {'type': 'block', 'statements': statements} + + def if_statement(self): + self.eat('KEYWORD_IF') + self.eat('LPAREN') + condition = self.expression() + self.eat('RPAREN') + then_branch = self.block() + else_branch = None + + if self.current_token.type == 'KEYWORD_ELSE': + self.eat('KEYWORD_ELSE') + if self.current_token.type == 'KEYWORD_IF': + else_branch = self.if_statement() + else: + else_branch = self.block() + + return { + 'type': 'if_statement', + 'condition': condition, + 'then_branch': then_branch, + 'else_branch': else_branch + } + + def while_statement(self): + self.eat('KEYWORD_WHILE') + self.eat('LPAREN') + condition = self.expression() + self.eat('RPAREN') + body = self.block() + return { + 'type': 'while_statement', + 'condition': condition, + 'body': body + } + + def return_statement(self): + self.eat('KEYWORD_RETURN') + value = self.expression() + # 检查是否需要分号(在函数体中可能不需要分号) + if self.current_token.type == 'SEMICOLON': + self.eat('SEMICOLON') + return { + 'type': 'return_statement', + 'value': value + } + + def def_statement(self): + self.eat('KEYWORD_DEF') + name = self.current_token.value + self.eat('IDENTIFIER') + self.eat('LPAREN') + parameters = [] + if self.current_token.type != 'RPAREN': + parameters.append(self.current_token.value) + self.eat('IDENTIFIER') + while self.current_token.type == 'COMMA': + self.eat('COMMA') + parameters.append(self.current_token.value) + self.eat('IDENTIFIER') + self.eat('RPAREN') + body = self.block() + return { + 'type': 'def_statement', + 'name': name, + 'parameters': parameters, + 'body': body + } + + def import_statement(self): + # Handle both @library and import library syntax + if self.current_token.type == 'AT': + self.eat('AT') + elif self.current_token.type == 'KEYWORD_IMPORT': + self.eat('KEYWORD_IMPORT') + module = self.current_token.value + self.eat('IDENTIFIER') + # Check if there's a semicolon, but don't require it + if self.current_token.type == 'SEMICOLON': + self.eat('SEMICOLON') + return { + 'type': 'import_statement', + 'module': module + } + + def include_directive(self): + module = self.current_token.value + self.eat('INCLUDE_DIRECTIVE') + # Check if there's a semicolon, but don't require it + if self.current_token.type == 'SEMICOLON': + self.eat('SEMICOLON') + return { + 'type': 'include_directive', + 'module': module + } + + def class_statement(self): + self.eat('KEYWORD_CLASS') + name = self.current_token.value + self.eat('IDENTIFIER') + self.eat('LBRACE') + methods = [] + while self.current_token.type != 'RBRACE' and self.current_token.type != 'EOF': + if self.current_token.type == 'KEYWORD_DEF': + methods.append(self.def_statement()) + else: + self.error(f"Expected function definition in class, got {self.current_token.type}") + self.eat('RBRACE') + return { + 'type': 'class_statement', + 'name': name, + 'methods': methods + } + + def statement(self): + if self.current_token.type == 'INCLUDE_DIRECTIVE': + return self.include_directive() + elif self.current_token.type == 'KEYWORD_SHOW': + return self.function_call() + elif self.current_token.type == 'KEYWORD_IF': + return self.if_statement() + elif self.current_token.type == 'KEYWORD_WHILE': + return self.while_statement() + elif self.current_token.type == 'KEYWORD_DEF': + return self.def_statement() + elif self.current_token.type == 'KEYWORD_CLASS': + return self.class_statement() + elif self.current_token.type == 'KEYWORD_RETURN': + return self.return_statement() + elif self.current_token.type == 'KEYWORD_IMPORT': + return self.import_statement() + elif self.current_token.type == 'AT': + return self.import_statement() + elif self.current_token.type == 'IDENTIFIER': + # Check if it's a function call + if self.lexer.position + 1 < len(self.lexer.source) and self.lexer.source[self.lexer.position] == '(': + # It's a function call + name = self.current_token.value + self.eat('IDENTIFIER') + self.eat('LPAREN') + arguments = [] + if self.current_token.type != 'RPAREN': + arguments.append(self.expression()) + while self.current_token.type == 'COMMA': + self.eat('COMMA') + arguments.append(self.expression()) + self.eat('RPAREN') + self.eat('SEMICOLON') + return { + 'type': 'function_call', + 'name': name, + 'arguments': arguments + } + else: + # It's an assignment + return self.assignment() + else: + self.error(f"Unexpected token {self.current_token.type}") + + def parse(self): + statements = [] + while self.current_token.type != 'EOF': + statements.append(self.statement()) + return {'type': 'block', 'statements': statements} + +class Interpreter: + def __init__(self): + import math + import time + import random + import os + import subprocess + import hashlib + + self.symbols = {} + self.functions = { + # 字符串和数组函数 + 'len': lambda s: str(len(eval(s)) if s.startswith('[') and s.endswith(']') else len(s)), + 'upper': lambda s: s.upper(), + 'lower': lambda s: s.lower(), + 'substring': lambda s, start, end: s[int(start):int(end)], + # 数组函数 + 'arr': lambda *args: str(list(args)), + 'push': lambda arr, item: str(eval(arr) + [item]), + 'pop': lambda arr: str(eval(arr)[:-1]), + 'indexOf': lambda arr, item: str(eval(arr).index(item) if item in eval(arr) else -1), + 'join': lambda arr, sep: sep.join(eval(arr)), + 'reverse': lambda arr: str(list(reversed(eval(arr)))), + 'sort': lambda arr: str(sorted(eval(arr))), + 'slice': lambda arr, start, end: str(eval(arr)[int(start):int(end)]), + # 数学库 + 'math.abs': lambda x: str(abs(float(x))), + 'math.sqrt': lambda x: str(float(x) ** 0.5), + 'math.pow': lambda x, y: str(float(x) ** float(y)), + 'math.sin': lambda x: str(math.sin(math.radians(float(x)))), + 'math.cos': lambda x: str(math.cos(math.radians(float(x)))), + 'math.tan': lambda x: str(math.tan(math.radians(float(x)))), + 'math.asin': lambda x: str(math.degrees(math.asin(float(x)))), + 'math.acos': lambda x: str(math.degrees(math.acos(float(x)))), + 'math.atan': lambda x: str(math.degrees(math.atan(float(x)))), + 'math.pi': lambda: str(math.pi), + 'math.e': lambda: str(math.e), + 'math.max': lambda *args: str(max(float(arg) for arg in args)), + 'math.min': lambda *args: str(min(float(arg) for arg in args)), + 'math.random': lambda: str(random.random()), + 'math.randint': lambda a, b: str(random.randint(int(a), int(b))), + # 日期时间库 + 'time.now': lambda: str(int(time.time())), + 'time.sleep': lambda seconds: (time.sleep(float(seconds)), 'Done')[1], + 'time.format': lambda timestamp, format_str: time.strftime(format_str, time.localtime(float(timestamp))), + # JSON库 + 'json.parse': lambda json_str: str(eval(json_str)), + 'json.stringify': lambda obj: str(obj), + # 加密库 + 'crypto.md5': lambda s: hashlib.md5(s.encode()).hexdigest(), + 'crypto.sha1': lambda s: hashlib.sha1(s.encode()).hexdigest(), + 'crypto.sha256': lambda s: hashlib.sha256(s.encode()).hexdigest(), + # 字符串操作库 + 'string.split': lambda s, sep: str(list(s)) if sep == '' else str(s.split(sep)), + 'string.upper': lambda s: s.upper(), + 'string.lower': lambda s: s.lower(), + 'string.trim': lambda s: s.strip(), + 'string.replace': lambda s, old, new: s.replace(old, new), + 'string.startsWith': lambda s, prefix: str(s.startswith(prefix)), + 'string.endsWith': lambda s, suffix: str(s.endswith(suffix)), + 'string.substring': lambda s, start, end: s[int(start):int(end)], + 'string.indexOf': lambda s, substr: str(s.find(substr)), + 'string.length': lambda s: str(len(s)), + 'string.contains': lambda s, substr: str(substr in s), + 'string.concat': lambda s1, s2: s1 + s2, + 'string.repeat': lambda s, n: s * int(n), + 'string.reverse': lambda s: s[::-1], + 'string.includes': lambda s, substr: str(substr in s), + 'string.lastIndexOf': lambda s, substr: str(s.rfind(substr)), + 'string.padStart': lambda s, length, char: s.rjust(int(length), char), + 'string.padEnd': lambda s, length, char: s.ljust(int(length), char), + # 数组操作库 + 'array.length': lambda arr: str(len(eval(arr))), + 'array.push': lambda arr, item: str(eval(arr) + [item]), + 'array.pop': lambda arr: str(eval(arr)[:-1]), + 'array.shift': lambda arr: str(eval(arr)[1:]), + 'array.unshift': lambda arr, item: str([item] + eval(arr)), + 'array.indexOf': lambda arr, item: str(eval(arr).index(item) if item in eval(arr) else -1), + 'array.lastIndexOf': lambda arr, item: str(len(eval(arr)) - 1 - eval(arr)[::-1].index(item) if item in eval(arr) else -1), + 'array.includes': lambda arr, item: str(item in eval(arr)), + 'array.join': lambda arr, sep: sep.join(eval(arr)), + 'array.reverse': lambda arr: str(list(reversed(eval(arr)))), + 'array.sort': lambda arr: str(sorted(eval(arr))), + 'array.slice': lambda arr, start, end: str(eval(arr)[int(start):int(end)]), + # 工具库 + 'util.random': lambda: str(random.random()), + 'util.randint': lambda a, b: str(random.randint(int(a), int(b))), + 'util.round': lambda x, n=0: str(round(float(x), int(n))), + 'util.abs': lambda x: str(abs(float(x))), + 'util.min': lambda *args: str(min(float(arg) for arg in args)), + 'util.max': lambda *args: str(max(float(arg) for arg in args)), + 'util.sum': lambda *args: str(sum(float(arg) for arg in args)), + 'util.average': lambda *args: str(sum(float(arg) for arg in args) / len(args)) if args else '0', + 'util.isNumber': lambda x: str(x.replace('.', '', 1).replace('-', '', 1).isdigit()), + 'util.isString': lambda x: str(isinstance(x, str)), + 'util.isArray': lambda x: str(x.startswith('[') and x.endswith(']')), + 'util.toNumber': lambda x: str(float(x)), + 'util.toString': lambda x: str(x), + 'util.typeof': lambda x: 'array' if x.startswith('[') and x.endswith(']') else 'number' if x.replace('.', '', 1).replace('-', '', 1).isdigit() else 'string' if isinstance(x, str) else 'unknown', + # 其他函数 + 'input': lambda prompt: input(prompt), + 'int': lambda s: str(int(s)), + 'str': lambda x: str(x), + # 网络请求函数 + 'http_get': self.http_get, + 'http_post': self.http_post, + # 文件操作函数 + 'file_read': self.file_read, + 'file_write': self.file_write, + 'file_exists': self.file_exists, + # 系统函数 + 'system': self.system, + 'get_env': self.get_env + } + self.classes = {} + self.return_value = None + self.modules = {} + + # 模块引用 + self.math = math + self.time = time + self.random = random + self.os = os + self.subprocess = subprocess + self.hashlib = hashlib + + def error(self, message, line=0, column=0): + # For Windows PowerShell compatibility, use simple error messages without ANSI codes + error_msg = "Runtime Error" + if line > 0 and column > 0: + error_msg += f" at line {line}, column {column}" + error_msg += f": {message}" + raise Exception(error_msg) + + def http_get(self, url): + """Send HTTP GET request""" + try: + import urllib.request + import json + with urllib.request.urlopen(url) as response: + data = response.read().decode('utf-8') + return data + except Exception as e: + return f"Error: {str(e)}" + + def http_post(self, url, data): + """Send HTTP POST request""" + try: + import urllib.request + import urllib.parse + import json + data = data.encode('utf-8') + req = urllib.request.Request(url, data=data, method='POST') + req.add_header('Content-Type', 'application/json') + with urllib.request.urlopen(req) as response: + response_data = response.read().decode('utf-8') + return response_data + except Exception as e: + return f"Error: {str(e)}" + + def file_read(self, filename): + """Read file content""" + try: + with open(filename, 'r', encoding='utf-8') as f: + content = f.read() + return content + except Exception as e: + return f"Error: {str(e)}" + + def file_write(self, filename, content): + """Write content to file""" + try: + with open(filename, 'w', encoding='utf-8') as f: + f.write(content) + return "Success" + except Exception as e: + return f"Error: {str(e)}" + + def file_exists(self, filename): + """Check if file exists""" + try: + return str(1 if self.os.path.exists(filename) else 0) + except Exception as e: + return f"Error: {str(e)}" + + def time_now(self): + """Get current time""" + try: + return str(self.time.time()) + except Exception as e: + return f"Error: {str(e)}" + + def time_sleep(self, seconds): + """Sleep for specified seconds""" + try: + self.time.sleep(float(seconds)) + return "Success" + except Exception as e: + return f"Error: {str(e)}" + + def random_int(self, min_val, max_val): + """Generate random integer between min and max""" + try: + return str(self.random.randint(int(min_val), int(max_val))) + except Exception as e: + return f"Error: {str(e)}" + + def random_float(self): + """Generate random float between 0 and 1""" + try: + return str(self.random.random()) + except Exception as e: + return f"Error: {str(e)}" + + def system(self, command): + """Execute system command""" + try: + result = self.subprocess.run(command, shell=True, capture_output=True, text=True) + return result.stdout + result.stderr + except Exception as e: + return f"Error: {str(e)}" + + def get_env(self, var_name): + """Get environment variable""" + try: + value = self.os.environ.get(var_name, "") + return value + except Exception as e: + return f"Error: {str(e)}" + + def evaluate(self, node): + if node['type'] == 'literal': + return node['value'] + elif node['type'] == 'identifier': + if node['name'] in self.symbols: + return self.symbols[node['name']] + else: + raise Exception(f"Undefined variable: {node['name']}") + elif node['type'] == 'array_index': + # Handle array indexing + array_value = self.symbols.get(node['name']) + if array_value is None: + raise Exception(f"Undefined variable: {node['name']}") + # Evaluate the index expression + index_value = self.evaluate(node['index']) + # Convert array string to actual list + try: + array = eval(array_value) + if isinstance(array, list): + index = int(index_value) + if 0 <= index < len(array): + return array[index] + else: + raise Exception(f"Array index out of range: {index}") + else: + raise Exception(f"Variable {node['name']} is not an array") + except Exception as e: + raise Exception(f"Error accessing array: {str(e)}") + elif node['type'] == 'binary_op': + left = self.evaluate(node['left']) + right = self.evaluate(node['right']) + + # Check if both operands are numbers for arithmetic operations + if left.isdigit() and right.isdigit(): + left_num = int(left) + right_num = int(right) + + if node['op'] == '+': + return str(left_num + right_num) + elif node['op'] == '-': + return str(left_num - right_num) + elif node['op'] == '*': + return str(left_num * right_num) + elif node['op'] == '/': + if right_num == 0: + raise Exception("Division by zero") + return str(left_num // right_num) + elif node['op'] == '==': + return str(1 if left_num == right_num else 0) + elif node['op'] == '!=': + return str(1 if left_num != right_num else 0) + elif node['op'] == '<': + return str(1 if left_num < right_num else 0) + elif node['op'] == '<=': + return str(1 if left_num <= right_num else 0) + elif node['op'] == '>': + return str(1 if left_num > right_num else 0) + elif node['op'] == '>=': + return str(1 if left_num >= right_num else 0) + else: + # String operations + if node['op'] == '+': + return left + right + elif node['op'] == '==': + return str(1 if left == right else 0) + elif node['op'] == '!=': + return str(1 if left != right else 0) + else: + raise Exception("Unsupported operation for non-numeric values") + + # Logical operations + if node['op'] == '&&': + return str(1 if (int(left) != 0 and int(right) != 0) else 0) + elif node['op'] == '||': + return str(1 if (int(left) != 0 or int(right) != 0) else 0) + elif node['type'] == 'function_call': + # Handle function calls in expressions + if node['name'] in self.symbols and callable(self.symbols[node['name']]): + # Check if it's a function from a module (like pylang or bflang) + func = self.symbols[node['name']] + args = [self.evaluate(arg) for arg in node['arguments']] + return func(*args) + elif node['name'] in self.functions: + # Check if it's a built-in function (lambda) + func = self.functions[node['name']] + if callable(func): + # Check if it's a library function that requires import + if '.' in node['name']: + # Extract library name from function name (e.g., math.abs -> math) + library_name = node['name'].split('.')[0] + # Check if the library is imported + if library_name not in self.modules: + self.error(f"Library '{library_name}' not imported. Use '@{library_name}' to import it.") + # Check if it's an HTTP function that requires http module + if (node['name'] == 'http_get' or node['name'] == 'http_post') and 'http' not in self.modules: + self.error("HTTP module not imported. Use '@http' to import it.") + # It's a built-in function + args = [self.evaluate(arg) for arg in node['arguments']] + return func(*args) + else: + # It's a user-defined function + # Create local symbol table + local_symbols = self.symbols.copy() + # Assign arguments to parameters + for i, param in enumerate(func['parameters']): + if i < len(node['arguments']): + local_symbols[param] = self.evaluate(node['arguments'][i]) + else: + local_symbols[param] = '0' # Default value + # Save current symbol table and return value + old_symbols = self.symbols + old_return_value = self.return_value + # Set local symbol table + self.symbols = local_symbols + # Reset return value + self.return_value = None + # Execute function body + self.interpret(func['body']) + # Get return value + return_value = self.return_value + # Restore symbol table and return value + self.symbols = old_symbols + self.return_value = old_return_value + # Return the value (ensure it's a string) + if return_value is not None: + return str(return_value) + return return_value + else: + self.error(f"Unknown function: {node['name']}") + else: + raise Exception(f"Unknown node type: {node['type']}") + + def interpret(self, node): + if isinstance(node, list): + for stmt in node: + result = self.interpret(stmt) + if result is not None: + return result + elif node['type'] == 'block': + for stmt in node['statements']: + result = self.interpret(stmt) + if result is not None: + return result + elif node['type'] == 'assignment': + value = self.evaluate(node['value']) + self.symbols[node['name']] = value + elif node['type'] == 'function_call': + if node['name'] == 'show': + for arg in node['arguments']: + value = self.evaluate(arg) + print(value, end=' ') + print() + elif node['name'] in self.symbols and callable(self.symbols[node['name']]): + # Check if it's a function from a module (like pylang or bflang) + func = self.symbols[node['name']] + args = [self.evaluate(arg) for arg in node['arguments']] + result = func(*args) + return result + elif node['name'] in self.functions: + # Check if it's a built-in function (lambda) + func = self.functions[node['name']] + if callable(func): + # Check if it's a library function that requires import + if '.' in node['name']: + # Extract library name from function name (e.g., math.abs -> math) + library_name = node['name'].split('.')[0] + # Check if the library is imported + if library_name not in self.modules: + self.error(f"Library '{library_name}' not imported. Use '@{library_name}' to import it.") + # Check if it's an HTTP function that requires http module + if (node['name'] == 'http_get' or node['name'] == 'http_post') and 'http' not in self.modules: + self.error("HTTP module not imported. Use '@http' to import it.") + # It's a built-in function + args = [self.evaluate(arg) for arg in node['arguments']] + result = func(*args) + return result + else: + # It's a user-defined function + # Create local symbol table + local_symbols = self.symbols.copy() + # Assign arguments to parameters + for i, param in enumerate(func['parameters']): + if i < len(node['arguments']): + local_symbols[param] = self.evaluate(node['arguments'][i]) + else: + local_symbols[param] = '0' # Default value + # Save current symbol table and return value + old_symbols = self.symbols + old_return_value = self.return_value + # Set local symbol table + self.symbols = local_symbols + # Reset return value + self.return_value = None + # Execute function body + self.interpret(func['body']) + # Get return value + return_value = self.return_value + # Restore symbol table and return value + self.symbols = old_symbols + self.return_value = old_return_value + # Return the value (ensure it's a string) + if return_value is not None: + return str(return_value) + return return_value + elif node['name'] in self.functions and 'math.' in node['name']: + # Check if it's a math function + func = self.functions[node['name']] + if callable(func): + # It's a math function + args = [self.evaluate(arg) for arg in node['arguments']] + result = func(*args) + return result + else: + self.error(f"Unknown function: {node['name']}") + elif node['type'] == 'if_statement': + condition = self.evaluate(node['condition']) + if int(condition) != 0: + self.interpret(node['then_branch']) + elif node['else_branch']: + self.interpret(node['else_branch']) + elif node['type'] == 'while_statement': + while True: + condition = self.evaluate(node['condition']) + if int(condition) == 0: + break + self.interpret(node['body']) + # Check for return statement + if self.return_value is not None: + break + elif node['type'] == 'def_statement': + # Store function definition + self.functions[node['name']] = { + 'parameters': node['parameters'], + 'body': node['body'] + } + elif node['type'] == 'class_statement': + # Store class definition + self.classes[node['name']] = { + 'methods': {} + } + for method in node['methods']: + self.classes[node['name']]['methods'][method['name']] = { + 'parameters': method['parameters'], + 'body': method['body'] + } + elif node['type'] == 'return_statement': + # Set return value + self.return_value = self.evaluate(node['value']) + elif node['type'] == 'import_statement': + # Handle import statement + module = node['module'] + self.modules[module] = True + # Here you can add module-specific initialization + if module == 'http': + # HTTP module is already initialized in functions + pass + elif module == 'math': + # Add math functions to the current scope + pass + elif node['type'] == 'include_directive': + # Handle include directive + module = node['module'] + self.modules[module] = True + + # Check if it's a built-in module + if module == 'response': + # Enable HTTP request functions + pass + else: + # Try to load from includes folder + import os + include_path = os.path.join('includes', f'{module}.oraset') + if os.path.exists(include_path): + try: + with open(include_path, 'r', encoding='utf-8') as f: + source = f.read() + # Parse and interpret the included file + lexer = Lexer(source) + parser = Parser(lexer) + ast = parser.parse() + self.interpret(ast) + print(f"Successfully included module: {module}") + except Exception as e: + print(f"Error including module {module}: {e}") + else: + print(f"Warning: Module {module} not found in includes folder") + else: + raise Exception(f"Unknown node type: {node['type']}") + +import base64 +import zlib + +def pack_file(input_file, output_file): + """Pack .oas file into .osp file""" + try: + with open(input_file, 'r', encoding='utf-8') as f: + source = f.read() + + # Compress the source code + compressed = zlib.compress(source.encode('utf-8')) + # Encode as base64 + encoded = base64.b64encode(compressed) + + with open(output_file, 'wb') as f: + f.write(encoded) + + print(f"Successfully packed {input_file} into {output_file}") + return 0 + except Exception as e: + print(f"Error packing file: {e}") + return 1 + +def unpack_file(input_file): + """Unpack .osp file and return the source code""" + try: + with open(input_file, 'rb') as f: + encoded = f.read() + + # Decode from base64 + compressed = base64.b64decode(encoded) + # Decompress + source = zlib.decompress(compressed).decode('utf-8') + + return source + except Exception as e: + print(f"Error unpacking file: {e}") + return None + +def main(): + if len(sys.argv) < 2: + print(f"Usage:") + print(f" {sys.argv[0]} # Run .oas or .oraset file") + print(f" {sys.argv[0]} pack # Pack .oas or .oraset file into .osp") + print(f" {sys.argv[0]} -osp # Run .osp file") + return 1 + + if sys.argv[1] == 'pack': + if len(sys.argv) != 3: + print(f"Usage: {sys.argv[0]} pack ") + return 1 + input_file = sys.argv[2] + output_file = input_file.replace('.oas', '.osp').replace('.oraset', '.osp') + return pack_file(input_file, output_file) + elif sys.argv[1] == '-osp': + if len(sys.argv) != 3: + print(f"Usage: {sys.argv[0]} -osp ") + return 1 + input_file = sys.argv[2] + source = unpack_file(input_file) + if source is None: + return 1 + else: + filename = sys.argv[1] + try: + with open(filename, 'r', encoding='utf-8') as f: + source = f.read() + except FileNotFoundError: + print(f"Error: Could not open file {filename}") + return 1 + + lexer = Lexer(source) + parser = Parser(lexer) + try: + ast = parser.parse() + except Exception as e: + print(f"Error: {e}") + return 1 + + interpreter = Interpreter() + try: + interpreter.interpret(ast) + except Exception as e: + print(f"Error: {e}") + return 1 + + return 0 + +if __name__ == "__main__": + sys.exit(main())