Skip to content

Commit c1f4b9e

Browse files
committed
Merge branch 'main' into Xmader/fix/readline-windows
2 parents e46bcc0 + 36ceee2 commit c1f4b9e

35 files changed

+944
-12
lines changed

.eslintrc.js

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
/**
2+
* @file .eslintrc.js - ESLint configuration file, following the Core Team's JS Style Guide.
3+
* @author Wes Garland <[email protected]>
4+
* @date Mar. 2022, July 2023
5+
*/
6+
7+
/**
8+
* @see {@link https://eslint.org/docs/latest/use/configure/}
9+
* @type {import('eslint').Linter.Config}
10+
*/
11+
module.exports = {
12+
extends: 'eslint:recommended',
13+
globals: {
14+
'python': true
15+
},
16+
env: {
17+
browser: true,
18+
commonjs: true,
19+
es2021: true,
20+
node: true
21+
},
22+
parserOptions: {
23+
ecmaVersion: 13,
24+
sourceType: 'script',
25+
},
26+
rules: {
27+
'indent': [ 'warn', 2, {
28+
SwitchCase: 1,
29+
ignoredNodes: ['CallExpression', 'ForStatement'],
30+
}
31+
],
32+
'linebreak-style': [ 'error', 'unix' ],
33+
'quotes': [ 'warn', 'single' ],
34+
'func-call-spacing': [ 'off', 'never' ],
35+
'no-prototype-builtins': 'off',
36+
'quotes': ['warn', 'single', 'avoid-escape'],
37+
'no-empty': [ 'warn' ],
38+
'no-multi-spaces': [ 'off' ],
39+
'prettier/prettier': [ 'off' ],
40+
'vars-on-top': [ 'error' ],
41+
'no-var': [ 'off' ],
42+
'spaced-comment': [ 'warn' ],
43+
'brace-style': [ 'off' ],
44+
'no-eval': [ 'error' ],
45+
'object-curly-spacing': [ 'warn', 'always' ],
46+
'eqeqeq': [ 'warn', 'always' ],
47+
'no-dupe-keys': [ 'warn' ],
48+
'no-constant-condition': [ 'warn' ],
49+
'no-extra-boolean-cast': [ 'warn' ],
50+
'no-sparse-arrays': [ 'off' ],
51+
'no-inner-declarations': [ 'off' ],
52+
'no-loss-of-precision': [ 'warn' ],
53+
'require-atomic-updates': [ 'warn' ],
54+
'no-dupe-keys': [ 'warn' ],
55+
'no-dupe-class-members': [ 'warn' ],
56+
'no-fallthrough': [ 'warn', { commentPattern: 'fall[ -]*through' }],
57+
'no-invalid-this': [ 'error' ],
58+
'no-return-assign': [ 'error' ],
59+
'no-return-await': [ 'warn' ],
60+
'no-unused-expressions': [ 'warn', { allowShortCircuit: true, allowTernary: true } ],
61+
'prefer-promise-reject-errors': [ 'error' ],
62+
'no-throw-literal': [ 'error' ],
63+
'semi': [ 'off', { omitLastInOneLineBlock: true }], /* does not work right with exports.X = function allmanStyle */
64+
'semi-style': [ 'warn', 'last' ],
65+
'semi-spacing': [ 'error', {'before': false, 'after': true}],
66+
'no-extra-semi': [ 'warn' ],
67+
'no-tabs': [ 'error' ],
68+
'symbol-description': [ 'error' ],
69+
'operator-linebreak': [ 'warn', 'before' ],
70+
'new-cap': [ 'warn' ],
71+
'consistent-this': [ 'error', 'that' ],
72+
'no-use-before-define': [ 'error', { functions: false, classes: false } ],
73+
'no-shadow': [ 'error' ],
74+
'no-label-var': [ 'error' ],
75+
'radix': [ 'error' ],
76+
'no-self-compare': [ 'error' ],
77+
'require-await': [ 'error' ],
78+
'require-yield': [ 'error' ],
79+
'no-promise-executor-return': [ 'off' ],
80+
'no-template-curly-in-string': [ 'warn' ],
81+
'no-unmodified-loop-condition': [ 'warn' ],
82+
'no-unused-private-class-members': [ 'warn' ],
83+
'no-use-before-define': [ 'error', { functions: false, classes: true, variables: true }],
84+
'no-implicit-coercion': [1, {
85+
disallowTemplateShorthand: false,
86+
boolean: true,
87+
number: true,
88+
string: true,
89+
allow: ['!!'] /* really only want to allow if(x) and if(!x) but not if(!!x) */
90+
}],
91+
'no-trailing-spaces': [ 'off', {
92+
skipBlankLines: true,
93+
ignoreComments: true
94+
}
95+
],
96+
'no-unused-vars': ['warn', {
97+
vars: 'all',
98+
args: 'none',
99+
ignoreRestSiblings: false
100+
}],
101+
}
102+
};

README.md

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -222,10 +222,10 @@ that if you update an object in JavaScript, the corresponding Dict in Python wil
222222
|:---------------------|:----------------|
223223
| string | String
224224
| number | Float
225-
| bigint | Integer
225+
| bigint | pythonmonkey.bigint (Integer)
226226
| boolean | Bool
227227
| function | Function
228-
| object - most | JSObjectProxy which inherits from Dict
228+
| object - most | pythonmonkey.JSObjectProxy (Dict)
229229
| object - Date | datetime
230230
| object - Array | List
231231
| object - Promise | awaitable
@@ -235,14 +235,23 @@ that if you update an object in JavaScript, the corresponding Dict in Python wil
235235

236236
## Tricks
237237
### Integer Type Coercion
238-
You can force a number in JavaScript to be coerced as an integer by casting it to BigInt.
238+
You can force a number in JavaScript to be coerced as an integer by casting it to BigInt:
239239
```javascript
240240
function myFunction(a, b) {
241241
const result = calculate(a, b);
242242
return BigInt(Math.floor(result));
243243
}
244244
```
245245

246+
The `pythonmonkey.bigint` object works like an int in Python, but it will be coerced as a BigInt in JavaScript:
247+
```python
248+
import pythonmonkey
249+
250+
def fn myFunction()
251+
result = 5
252+
return pythonmonkey.bigint(result)
253+
```
254+
246255
### Symbol injection via cross-language IIFE
247256
You can use a JavaScript IIFE to create a scope in which you can inject Python symbols:
248257
```python

include/DateType.hh

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,19 @@
2626
struct DateType : public PyType {
2727
public:
2828
DateType(PyObject *object);
29-
DateType(JSContext *cx, JS::Handle<JSObject *> dateObj);
29+
/**
30+
* @brief Convert a JS Date object to Python datetime
31+
*/
32+
DateType(JSContext *cx, JS::HandleObject dateObj);
33+
3034
const TYPE returnType = TYPE::DATE;
35+
36+
/**
37+
* @brief Convert a Python datetime object to JS Date
38+
*
39+
* @param cx - javascript context pointer
40+
*/
41+
JSObject *toJsDate(JSContext *cx);
3142
};
3243

3344
#endif

peter-jr

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -151,8 +151,8 @@ findTests \
151151

152152
if [ "${ext}" = "failing" ]; then
153153
knownFail="yes"
154-
PASS="$(bggreen PASS) (unexpected)"
155-
FAIL="$(dred F̶A̶I̶L̶) (expected)"
154+
PASS="$(bggreen PASS) $(grey \(unexpected\))"
155+
FAIL="$(dred F̶A̶I̶L̶) $(grey \(expected\))"
156156
[ ! "${VERBOSE}" ] && thisStderr="/dev/null"
157157
else
158158
knownFail=""

python/pythonmonkey/cli/pmjs.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,9 +44,17 @@
4444
pythonCmd.serial = 0;
4545
return;
4646
}
47+
48+
if (cmd === '')
49+
{
50+
return;
51+
}
52+
4753
try {
4854
if (arguments[0] === 'from' || arguments[0] === 'import')
49-
return python.exec(cmd);
55+
{
56+
return python.exec(cmd);
57+
}
5058
5159
const retval = python.eval(cmd);
5260
}

src/DateType.cc

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111

1212
DateType::DateType(PyObject *object) : PyType(object) {}
1313

14-
DateType::DateType(JSContext *cx, JS::Handle<JSObject *> dateObj) {
14+
DateType::DateType(JSContext *cx, JS::HandleObject dateObj) {
1515
JS::Rooted<JS::ValueArray<0>> args(cx);
1616
JS::Rooted<JS::Value> year(cx);
1717
JS::Rooted<JS::Value> month(cx);
@@ -33,4 +33,12 @@ DateType::DateType(JSContext *cx, JS::Handle<JSObject *> dateObj) {
3333
year.toNumber(), month.toNumber() + 1, day.toNumber(),
3434
hour.toNumber(), minute.toNumber(), second.toNumber(),
3535
usecond.toNumber() * 1000);
36-
}
36+
}
37+
38+
JSObject *DateType::toJsDate(JSContext *cx) {
39+
// See https://docs.python.org/3/library/datetime.html#datetime.datetime.timestamp
40+
PyObject *timestamp = PyObject_CallMethod(pyObject, "timestamp", NULL); // the result is in seconds
41+
double milliseconds = PyFloat_AsDouble(timestamp) * 1000;
42+
Py_DECREF(timestamp);
43+
return JS::NewDateObject(cx, JS::TimeClip(milliseconds));
44+
}

src/jsTypeFactory.cc

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
#include "include/StrType.hh"
2121
#include "include/IntType.hh"
2222
#include "include/PromiseType.hh"
23+
#include "include/DateType.hh"
2324
#include "include/ExceptionType.hh"
2425
#include "include/BufferType.hh"
2526

@@ -28,6 +29,7 @@
2829
#include <js/Proxy.h>
2930

3031
#include <Python.h>
32+
#include <datetime.h> // https://docs.python.org/3/c-api/datetime.html
3133

3234
#define HIGH_SURROGATE_START 0xD800
3335
#define LOW_SURROGATE_START 0xDC00
@@ -65,6 +67,8 @@ size_t UCS4ToUTF16(const uint32_t *chars, size_t length, uint16_t **outStr) {
6567
}
6668

6769
JS::Value jsTypeFactory(JSContext *cx, PyObject *object) {
70+
PyDateTime_IMPORT; // for PyDateTime_Check
71+
6872
JS::RootedValue returnType(cx);
6973

7074
if (PyBool_Check(object)) {
@@ -145,6 +149,10 @@ JS::Value jsTypeFactory(JSContext *cx, PyObject *object) {
145149
JSObject *error = ExceptionType(object).toJsError(cx);
146150
returnType.setObject(*error);
147151
}
152+
else if (PyDateTime_Check(object)) {
153+
JSObject *dateObj = DateType(object).toJsDate(cx);
154+
returnType.setObject(*dateObj);
155+
}
148156
else if (PyObject_CheckBuffer(object)) {
149157
BufferType *pmBuffer = new BufferType(object);
150158
JSObject *typedArray = pmBuffer->toJsTypedArray(cx); // may return null

src/modules/pythonmonkey/pythonmonkey.cc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -375,7 +375,7 @@ static bool clearTimeout(JSContext *cx, unsigned argc, JS::Value *vp) {
375375
// Retrieve the AsyncHandle by `timeoutID`
376376
int32_t timeoutID = timeoutIdArg.toInt32();
377377
AsyncHandle *handle = AsyncHandle::fromId((uint32_t)timeoutID);
378-
if (!handle) return true; // does nothing on invalid timeoutID
378+
if (!handle) return true; // does nothing on invalid timeoutID
379379

380380
// Cancel this job on Python event-loop
381381
handle->cancel();

tests/js/console-smoke.simple

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,10 @@
88
* @author Wes Garland, [email protected]
99
* @date June 2023
1010
*/
11-
const console = new (require('console').Console)({stdout: python.stdout, stderr: python.stdout});
11+
const console = new (require('console').Console)({
12+
stdout: python.stdout,
13+
stderr: python.stdout
14+
});
1215
globalThis.console.log('one');
1316
globalThis.console.info('two');
1417
console.debug('three');
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
/**
2+
* @file js2py/array-chnage-index.simple
3+
* Simple test which demonstrates modifying an array
4+
* passed to Python and returning it.
5+
* @author Will Pringle, [email protected]
6+
* @date July 2023
7+
*/
8+
'use strict';
9+
10+
const numbers = [1,2,3,4,5,6,7,8,9];
11+
12+
python.exec(`
13+
def setArrayAtIndex(array, index, new):
14+
array[1] = new # can't index "array" based on "index"... probably because index is a float. So just pass "1"
15+
return array
16+
`);
17+
const setArrayAtIndex = python.eval("setArrayAtIndex")
18+
const numbersBack = setArrayAtIndex(numbers, 1, 999);
19+
20+
// check that the array data was modified by reference in python
21+
if (numbers[1] !== 999)
22+
throw new Error('array not modified by python');
23+
24+
// check that the array we get from python is the same reference as defined in js
25+
if (!Object.is(numbers, numbersBack))
26+
throw new Error('array reference differs between JavaScript and Python');
27+
28+
console.log('pass');
29+

0 commit comments

Comments
 (0)