5.2. نحو و معناشناسی
این بخش به بررسی نحو و معناشناسی زبان Nix میپردازد.
مقادیر ثابت پایه (Basic Literals)
رشته (String)
به مقالهی مقادیر ثابت رشتهای (String literals) مراجعه کنید.
عدد (Number)
اعداد میتوانند عدد صحیح (مانند 123) یا ممیز شناور (مانند 123.43 یا .27e13) باشند.
اعداد صحیح در زبان Nix اعداد صحیح علامتدار ۶۴ بیتی با مکمل دو هستند و بازه بین ۹۲۲۳۳۷۲۰۳۶۸۵۴۷۷۵۸۰۸- تا ۹۲۲۳۳۷۲۰۳۶۸۵۴۷۷۵۸۰۷ (شامل خود این اعداد) را پوشش میدهند.
توجه داشته باشید که مقادیر ثابت عددی منفی در واقع به عنوان عمل منفیسازی تکعملوندی روی مقادیر ثابت عددی مثبت تجزیه (parse) میشوند.
این بدان معناست که کوچکترین عدد صحیح یعنی -9223372036854775808 را نمیتوان مستقیماً به عنوان یک مقدار ثابت نوشت، زیرا عدد مثبت 9223372036854775808 یک واحد از حداکثر بازه فراتر است.
برای اطلاع از معناشناسی، به عملگرهای حسابی و مقایسهای مراجعه کنید.
مسیر (Path)
مسیرها را میتوان با استفاده از مقادیر ثابت مسیر مانند ./builder.sh بیان کرد.
یک مقدار ثابت مسیر برای اینکه به عنوان مسیر شناخته شود، باید حداقل شامل یک اسلش باشد.
برای نمونه، builder.sh یک مسیر نیست:
بلکه به عنوان عبارتی تجزیه میشود که صفت sh را از متغیر builder انتخاب میکند.
مقادیر ثابت مسیر نسبت به پوشه پایه خود حل میشوند. مقادیر ثابت مسیر همچنین میتوانند با شروع شدن با یک اسلش، به مسیرهای مطلق اشاره کنند.
توجه
مسیرهای مطلق قابلیت حمل (portability) عبارتها را کاهش میدهند. در مواردی که یک تابع، یک مقدار ثابت مسیر را به یک رشته مسیر مطلق برای یک فایل پیکربندی تبدیل میکند، توصیه میشود به جای آن یک مقدار ثابت رشتهای بنویسید. این کار از بروز هرگونه سردرگمی در مورد اینکه آیا فایلهای آن مکان در طول ارزیابی استفاده خواهند شد یا خیر، جلوگیری میکند. همچنین از موقعیتهای ناخواستهای که در آن ممکن است تابعی سعی کند تمام محتویات آن مکان را در انبار کپی کند، جلوگیری به عمل میآورد.
اگر اولین مؤلفه یک مسیر ~ باشد، به گونهای تفسیر میشود که بقیه مسیر نسبت به پوشه خانگی کاربر باشد.
به عنوان مثال، برای کاربری که پوشه خانگیاش /home/edolstra است، ~/foo معادل /home/edolstra/foo خواهد بود.
مقادیر ثابت مسیری که با ~ شروع میشوند، در ارزیابی خالص (Pure) مجاز نیستند.
مقادیر ثابت مسیر علاوه بر اینکه میتوانند درونگذاری رشته شوند، میتوانند شامل درونگذاری درون سایر عبارتها نیز باشند.
برای اینکه نتیجه به عنوان یک مسیر شناخته شود، حداقل یک اسلش (/) باید پیش از هر عبارت درونگذاریشده ظاهر شود.
عبارت a.${'{'}foo{'}'}/b.$یک عملیات تقسیم عددی از نظر نحوی معتبر است.
در حالی که./a.${'{'}foo{'}'}/b.$یک مسیر است.
مقادیر ثابت مسیر جستجو مانند<nixpkgs> نیز به مقادیر مسیر حل میشوند.
فهرست (List)
فهرستها با قرار دادن فهرستی از مقادیر که با فاصلهها از هم جدا شدهاند، درون کروشهها تشکیل میشوند. برای مثال،
[ 123 ./foo.nix "abc" (f { x = y; }) ] فهرستی متشکل از چهار عنصر را تعریف میکند که آخرین مورد، نتیجهی فراخوانی تابع f است. توجه داشته باشید که فراخوانیهای تابع باید درون پرانتز قرار گیرند. اگر آنها حذف میشدند، مثلاً،
[ 123 ./foo.nix "abc" f { x = y; } ] نتیجه یک فهرست پنجعنصری خواهد بود که عنصر چهارم آن یک تابع و عنصر پنجم یک مجموعه است.
توجه داشته باشید که فهرستها فقط از نظر مقادیر تنبل (lazy) هستند، اما از نظر طول سختگیر (strict) هستند.
به عناصر موجود در یک فهرست میتوان با استفاده از builtins.elemAt دسترسی پیدا کرد.
مجموعه ویژگی (Attribute Set)
یک مجموعه ویژگی، مجموعهای از جفتهای نام-مقدار است که صفتها (attributes) نامیده میشوند.
مجموعههای ویژگی در داخل آکولاد ({'{'} {'}'}) نوشته میشوند.
نامهای صفت و مقادیر صفت با یک علامت مساوی (=) از یکدیگر جدا میشوند.
هر مقدار میتواند یک عبارت دلخواه باشد که با یک نقطه ویرگول (;) خاتمه مییابد.
نام صفت یک رشتهی بدون کانتکست است و با یک [نام] (یک شناسه یا رشتهی تحتالفظی) نشان داده میشود.
نحو (Syntax)
attrset →
{'{'}{ name=expr;}{'}'}
صفتها میتوانند به هر ترتیبی ظاهر شوند. نام هر صفت فقط یک بار میتواند در هر مجموعه ویژگی ظاهر شود.
مثال
این قطعه کد یک مجموعه ویژگی با صفتهای زیر را تعریف میکند:
xبا مقدار123که یک عدد صحیح استtextبا مقدار"Hello"که یک رشته استyکه مقدار آن حاصل اعمال تابعfروی مجموعه ویژگی{'{'} bla = 456; {'}'}است{ x = 123; text = "Hello"; y = f { bla = 456; }; }
صفات موجود در مجموعههای ویژگی تورتودرتو را میتوان با استفاده از مسیرهای صفت (attribute paths) نوشت.
نحو
attrset →
{'{'}{ attrpath=expr;}{'}'}
یک مسیر صفت، فهرستی از نامها است که با نقطه از یکدیگر جدا شدهاند.
نحو
attrpath = name {
.name }
مثال
{ a.b.c = 1; a.b.d = 2; }{ a = { b = { c = 1; d = 2; }; }; }
نامهای صفت (attribute) همچنین میتوانند به طور ضمنی با استفاده از inherit keyword تنظیم شوند.
مثال
{ inherit (builtins) true; }{ true = true; }
به صفات میتوان با استفاده از عملگر . دسترسی پیدا کرد.
مثال:
{ a = "Foo"; b = "Bar"; }.a این عبارت به "Foo" ارزیابی میشود.
ممکن است بتوان با استفاده از کلیدواژه or یک مقدار پیشفرض در انتخاب صفت (attribute) ارائه داد.
مثال:
{ a = "Foo"; b = "Bar"; }.c or "Xyzzy" { a = "Foo"; b = "Bar"; }.c.d.e.f.g or "Xyzzy" هر دو به "Xyzzy" ارزیابی خواهند شد زیرا هیچ صفت cی در مجموعه وجود ندارد.
شما میتوانید از رشتههای دلخواه با نقلقول دوجفت (double-quoted) به عنوان نامهای صفت (attribute) استفاده کنید:
{ "$!@#?" = 123; }."$!@#?" let bar = "bar"; in
{ "foo ${bar}" = 123; }."foo ${bar}" هر دو به 123 ارزیابی خواهند شد.
نامهای صفت از [string interpolation] پشتیبانی میکنند:
let bar = "foo"; in
{ foo = 123; }.${bar} let bar = "foo"; in
{ ${bar} = 123; }.foo هر دو مقدار 123 را ارزیابی خواهند کرد.
در حالت خاصی که نام یک صفت (attribute) درون یک اعلان مجموعه (set declaration) به null ارزیابی شود (که معمولاً یک خطا است، زیرا null را نمیتوان به یک رشته تبدیل کرد)، آن صفت به سادگی به مجموعه اضافه نمیشود:
{ ${if foo then "bar" else null} = true; } این عبارت در صورتی که foo به مقدار false ارزیابی شود، به {'{'}{'}'} ارزیابی خواهد شد.
مجموعهای که دارای یک صفت [__functor] باشد که مقدار آن قابلفراخوانی است (یعنی خودش یک تابع یا مجموعهای با یک صفت __functor است که مقدار آن قابلفراخوانی است)، میتواند طوری فراخوانی شود که انگار یک تابع است، در حالی که خود مجموعه به عنوان اولین آرگومان به آن ارسال میشود؛ برای مثال،
let add = { __functor = self: x: x + self.x; };
inc = add // { x = 1; }; # inc is { x = 1; __functor = (...) }
in inc 1 # equivalent of `add.__functor add 1` i.e. `1 + self.x` ارزیابی میشود به 2. این ویژگی را میتوان برای مثال، جهت الصاق فراداده (metadata) به یک تابع بدون نیاز به این که فراخواننده با آن به شکل خاصی برخورد کند، یا برای پیادهسازی شکلی از برنامهنویسی شیءگرا به کار برد.
مجموعههای بازگشتی
مجموعههای بازگشتی شبیه مجموعههای ویژگی معمولی هستند، با این تفاوت که ویژگیهای درون آنها میتوانند به یکدیگر ارجاع دهند.
rec-attrset =
rec {'{'}[ name=expr;`]...}`
مثال:
rec {
x = y;
y = 123;
}.x این عبارت به 123 ارزیابی میشود.
توجه داشته باشید که بدون rec، اتریبیوت x = y; به متغیر y در اسکوپ پیرامون (در صورت وجود) ارجاع خواهد داد، و اگر چنین متغیری وجود نداشته باشد نامعتبر خواهد بود. یعنی در یک مجموعه معمولی (غیربازگشتی)، صفتها به اسکوپ واژگانی اضافه نمیشوند؛ اما در یک مجموعه بازگشتی، اضافه میشوند.
مجموعههای بازگشتی طبیعتاً خطر بازگشت پایانناپذیر را به همراه دارند. برای مثال، عبارت
rec {
x = y;
y = x;
}.x با پیام خطای infinite recursion encountered دچار کرش خواهد شد.
عبارتهای let
یک عبارت let به شما اجازه میدهد متغیرهای محلی را برای یک عبارت تعریف کنید.
let-in =
let[ identifier = expr;]...inexpr
مثال:
let
x = "foo";
y = "bar";
in x + y این عبارت به "foobar" ارزیابی میشود.
همچنین یک سینتکس قدیمیتر دیگر برای عبارتهای let وجود دارد که نباید در کدهای جدید استفاده شود:
let =
let{'{'}identifier = expr;[ identifier = expr;]...{'}'}
در این شکل، مجموعه ویژگی بین {'{'} {'}'} به صورت بازگشتی است.
یکی از صفتها باید نام خاص body را داشته باشد که همان نتیجه عبارت است.
مثال:
let {
foo = bar;
bar = "baz";
body = foo;
} این عبارت به "baz" ارزیابی میشود.
به ارث بردن صفات
هنگام تعریف یک مجموعه ویژگی یا در یک عبارت let، اغلب کپی کردن متغیرها از حوزه واژگانی پیرامون (مثلاً وقتی میخواهید صفات را منتشر کنید) راحتتر است.
این کار را میتوان با استفاده از کلیدواژه inherit کوتاهتر کرد.
مثال:
let x = 123; in
{
inherit x;
y = 456;
} معادل است با
let x = 123; in
{
x = x;
y = 456;
} و هر دو به {'{'} x = 123; y = 456; {'}'} ارزیابی میشوند.
نکته
این کار به این دلیل عمل میکند که
xتوسط سازندهیletبه حوزه واژگانی (lexical scope) اضافه میشود.
همچنین امکان به ارث بردن صفات از یک مجموعه ویژگی دیگر نیز وجود دارد.
مثال:
در این قطعهکد از all-packages.nix،
graphviz = (import ../tools/graphics/graphviz) {
inherit fetchurl stdenv libpng libjpeg expat x11 yacc;
inherit (xorg) libXaw;
};
xorg = {
libX11 = ...;
libXaw = ...;
...
}
libpng = ...;
libjpg = ...;
... مجموعه (set) مورد استفاده در فراخوانی تابع برای تابعی که در ../tools/graphics/graphviz تعریف شده است، تعدادی متغیر را از محدوده (scope) پیرامون (fetchurl تا yacc) به ارث میبرد، اما همچنین libXaw (ویجتهای X Athena) را از مجموعه xorg به ارث میبرد.
...
inherit x y z;
inherit (src-set) a b c;
... معادل است با
...
x = x; y = y; z = z;
a = src-set.a; b = src-set.b; c = src-set.c;
... هنگام استفاده در زمان تعریف متغیرهای محلی در یک عبارت let یا در حین
تعریف یک مجموعه.
در یک عبارت let، میتوان از inherit برای آوردن گزینشی صفات خاصی از یک مجموعه به درون محدوده استفاده کرد. برای مثال
let
x = { a = 1; b = 2; };
inherit (builtins) attrNames;
in
{
names = attrNames x;
} معادل است با
let
x = { a = 1; b = 2; };
in
{
names = builtins.attrNames x;
} هر دو به {'{'} names = [ "a" "b" ]; {'}'} ارزیابی میشوند.
توابع
توابع دارای شکل زیر هستند:
pattern: body الگو مشخص میکند که آرگومان تابع چه شکلی باید باشد، و متغیرها را در بدنه به (بخشهایی از) آرگومان متصل میکند. سه نوع الگو وجود دارد:
- اگر یک الگو تنها یک شناسه (identifier) باشد، آنگاه تابع با هر آرگومانی مطابقت پیدا میکند. مثال:
let negate = x: !x;
concat = x: y: x + y;
in if negate true then concat "foo" "bar" else "" توجه داشته باشید که concat تابعی است که یک آرگومان را میگیرد و تابعی را برمیگرداند که آرگومان دیگری را میگیرد. این امکان پارامتردهی جزئی (یعنی پر کردن فقط برخی از آرگومانهای یک تابع) را فراهم میکند؛ به عنوان مثال،
map (concat "foo") [ "bar" "bla" "abc" ] ارزیابی میشود به [ "foobar" "foobla" "fooabc" ].
- یک الگوی مجموعه به فرم
{'{'} name1, name2, …, nameN {'}'}با مجموعهای حاوی صفتهای فهرستشده مطابقت میکند و مقادیر آن صفتها را به متغیرها در بدنه تابع متصل (bind) میکند. برای مثال، تابع
{ x, y, z }: z + y + x فقط میتواند با مجموعهای فراخوانی شود که دقیقاً حاوی صفات x، y و z باشد. هیچ صفت دیگری مجاز نیست. اگر میخواهید آرگومانهای اضافی را مجاز کنید، میتوانید از سهنقطه (...) استفاده کنید:
{ x, y, z, ... }: z + y + x این کار روی هر مجموعه ای که حداقل شامل سه صفت نامبرده باشد، کار میکند.
- امکان ارائه مقادیر پیشفرض برای صفتها وجود دارد که در این صورت میتوانند وجود نداشته باشند. یک مقدار پیشفرض با نوشتن
name ? eمشخص میشود که در آن e یک عبارت دلخواه است. برای مثال،
{ x, y ? "foo", z ? "bar" }: z + y + x مشخصکننده تابعی است که تنها به یک صفت (attribute) به نام x نیاز دارد، اما به صورت اختیاری y و z را نیز میپذیرد.
- الگوی
@راهی برای ارجاع به کل مقدار در حال تطبیق فراهم میکند:
args@{ x, y, z, ... }: z + y + x + args.a اما همچنین میتواند به صورت زیر نوشته شود:
{ x, y, z, ... } @ args: z + y + x + args.a در اینجا args به آرگومان همانطور که ارسال شده است متصل میشود که در ادامه با الگوی {'{'} x, y, z, ... {'}'} تطبیق داده میشود.
الگوی @ عمدتاً زمانی معنا پیدا میکند که با سه نقطه (...) استفاده شود؛ زیرا میتوانید به نامهای صفات به عنوان a، با استفاده از args.a دسترسی داشته باشید، که به عنوان یک صفت اضافی به تابع داده شده بود.
هشدار
عبارت
args@نامargsرا به مجموعه ویژگی ارسالشده به تابع متصل میکند. به طور خاص،argsشامل هیچیک از مقادیر پیشفرض مشخصشده با?در الگوی مجموعه تابع نیست.برای مثال
let f = args@{ a ? 23, ... }: [ a args ]; in f {}معادل است با
let f = args @ { ... }: [ (args.a or 23) args ]; in f {}و هر دو عبارت به مقدار زیر ارزیابی خواهند شد:
[ 23 {} ]
تمام اتصالات (bindings) معرفیشده توسط تابع، در سرتاسر عبارت تابع در محدودهٔ دید (scope) قرار دارند؛ و نه فقط در بدنهٔ آن. بنابراین میتوان از آنها در مقادیر پیشفرض استفاده کرد.
مثال
یک پارامتر (
x) در مقدار پیشفرض برای پارامتری دیگر (y) استفاده میشود:
> let
> f = { x, y ? [x] }: { inherit y; };
> in
> f { x = 3; }
> ```
>
> این عبارت به مقدار زیر ارزیابی میشود:
>
```nix
> {
> y = [ 3 ];
> }
> ```
> **مثال**
>
> اتصال الگوی `@` یعنی `args`، در مقدار پیشفرض برای یک پارامتر یعنی `x` استفاده میشود:
>
```nix
> let
> f = args@{ x ? args.a, ... }: x;
> in
> f { a = 1; }
> ```
>
> این عبارت به مقدار زیر ارزیابی میشود:
>
```nix
> 1
> ```
توجه داشته باشید که توابع نام ندارند. اگر میخواهید به آنها نامی بدهید،
میتوانید آنها را به یک صفت (attribute) متصل کنید، برای مثال:
```nix
let concat = { x, y }: x + y;
in concat { x = "foo"; y = "bar"; } شرطیها
شرطیها به شکل زیر هستند:
if e1 then e2 else e3 که در آن e1 عبارتی است که باید به یک مقدار بولی (true یا false) ارزیابی شود.
ادعاها (Assertions)
ادعاها معمولاً برای بررسی برقرار بودن برخی الزامات روی ویژگیها و وابستگیها یا بین آنها استفاده میشوند. آنها به این شکل هستند:
assert e1; e2 که در آن e1 عبارتی است که باید به یک مقدار بولی ارزیابی شود. اگر به true ارزیابی شود، e2 بازگردانده میشود؛ در غیر این صورت، ارزیابی عبارت متوقف شده و یک ردیابی پشته (backtrace) چاپ میشود.
در ادامه یک عبارت Nix برای بسته Subversion آورده شده است که نحوه استفاده از ادعاها (assertions) را نشان میدهد:.
{ localServer ? false
, httpServer ? false
, sslSupport ? false
, pythonBindings ? false
, javaSwigBindings ? false
, javahlBindings ? false
, stdenv, fetchurl
, openssl ? null, httpd ? null, db4 ? null, expat, swig ? null, j2sdk ? null
}:
assert localServer -> db4 != null; ①
assert httpServer -> httpd != null && httpd.expat == expat; ②
assert sslSupport -> openssl != null && (httpServer -> httpd.openssl == openssl); ③
assert pythonBindings -> swig != null && swig.pythonSupport;
assert javaSwigBindings -> swig != null && swig.javaSupport;
assert javahlBindings -> j2sdk != null;
stdenv.mkDerivation {
name = "subversion-1.1.1";
...
openssl = if sslSupport then openssl else null; ④
...
} موارد قابلتوجه عبارتند از:
این ادعا بیان میکند که اگر قرار است Subversion از مخزنهای محلی پشتیبانی کند، به Berkeley DB نیاز است. بنابراین، اگر تابع Subversion با آرگومان
localServerبرابر باtrueاما آرگومانdb4برابر باnullفراخوانی شود، ارزیابی با شکست مواجه میشود.توجه داشته باشید که
->همان عملگر بولی استلزام منطقی است.این یک شرط ظریفتر است: اگر Subversion با پشتیبانی از آپاچی (
httpServer) ساخته شود، کتابخانه Expat (یک کتابخانه XML) که توسط Subversion استفاده میشود باید همان کتابخانهای باشد که آپاچی استفاده میکند. دلیل این امر آن است که در این پیکربندی، کد Subversion در نهایت به کد آپاچی پیوند (link) میخورد و اگر کتابخانههای Expat با یکدیگر مطابقت نداشته باشند، ممکن است یک خطای پیوند در زمان ساخت یا زمان اجرا یا ناسازگاری رخ دهد.این ادعا میگوید که برای اینکه Subversion از پشتیبانی SSL برخوردار باشد (به طوری که بتواند به URLهای
httpsدسترسی داشته باشد)، باید یک کتابخانه OpenSSL ارسال شود. علاوه بر این، میگوید که اگر پشتیبانی از آپاچی فعال باشد، OpenSSL آپاچی باید با OpenSSL متعلق به Subversion مطابقت داشته باشد. (توجه داشته باشید که اگر پشتیبانی از آپاچی فعال نباشد، OpenSSL آپاچی برای ما اهمیتی ندارد.)عبارت شرطی در اینجا چندان به ادعاها مربوط نمیشود، اما اشاره به آن خالی از لطف نیست: این شرط تضمین میکند که اگر پشتیبانی از SSL غیرفعال باشد، درایویشن Subversion به OpenSSL وابسته نخواهد بود، حتی اگر مقداری غیر از
nullارسال شده باشد. این کار از ساخت مجدد غیرضروری Subversion در صورت تغییر OpenSSL جلوگیری میکند.
عبارتهای با (With-expressions)
یک عبارت با،
with e1; e2 مجموعه e1 را وارد حوزه واژگانی (lexical scope) عبارت e2 میکند. برای نمونه،
let as = { x = "foo"; y = "bar"; };
in with as; x + y ارزیابی میشود به "foobar" زیرا عبارت with صفات x و y را از as به محدوده واژگانی (lexical scope) در عبارت x + y اضافه میکند. رایجترین کاربرد with همراه با تابع import است. برای مثال،
with (import ./definitions.nix); ... تمام صفات تعریفشده در فایل definitions.nix را به گونهای در دسترس قرار میدهد که گویی بهصورت محلی در یک عبارت let تعریف شدهاند.
اتصالات (bindings) معرفیشده توسط with، اتصالاتی را که از راههای دیگر (مانند مثال زیر) معرفی شدهاند، بازنویسی (shadow) نمیکنند.
let a = 3; in with { a = 1; }; let a = 4; in with { a = 2; }; ... همان حوزه را برقرار میکند
let a = 1; in let a = 2; in let a = 3; in let a = 4; in ... متغیرهایی که از عبارتهای بیرونی with میآیند، سایهدار (shadow) میشوند:
with { a = "outer"; };
with { a = "inner"; };
a ارزیابی میشود به "inner".
نظرات
نظرات درونخطی با
#شروع میشوند و تا انتهای خط ادامه دارند.مثال
> # A number
> 2 # Equals 1 + 1
> ```
>
```shell
> 2
> ```
- نظرات بلوکی با `/*` شروع میشوند و تا اولین وقوع بعدی `*/` ادامه پیدا میکنند.
> **مثال**
>
```nix
> /*
> Block comments
> can span multiple lines.
> */ "hello"
> ```
>
```shell
> "hello"
> ```
این بدین معناست که کامنتهای بلوکی را نمیتوان به صورت تو در تو قرار داد.
> **مثال**
>
```nix
> /* /* nope */ */ 1
> ```
>
>
```shell
> error: syntax error, unexpected '*'
>
> at «string»:1:15:
>
> 1| /* /* nope */ *
> | ^
> ```
در نظر داشته باشید که نظرات (توضیحات) تو در تو را escape کرده و در پردازش پسین (post-processing) آنها را unescape کنید.
> **مثال**
>
```nix
> /* /* nested *\/ */ 1
> ```
>
```shell
> 1
> ```